c++ - 如何在 javascript (emscripten) 中覆盖 c++ malloc/free?
问题描述
我通过包装原始函数并仅添加 Console.log 来显示内存地址、大小和总分配内存来覆盖 Javascript(emscripten) 中的 Module._malloc 和 Module._free。
我发现新函数仅捕获对 Module._malloc 和 Module._free 的 Javascript 调用,而不会捕获对 malloc() 和 free() 的 c++ 级别调用。我想知道为什么。
根据 Ofria 先生在此处https://stackoverflow.com/a/34057348/4806940的回答,Module._malloc 和 Module._free 是 c++ 的 malloc() 和 free() 的转换后的等效代码。
我正在使用 emscripten 1.35.0
编辑:继承人我如何在 javascript 中包装函数
var _defaultMalloc = Module._malloc;
var _defaultFree = Module._free;
var _totalMemoryUsed = 0;
var _mallocTracker = {};
Module._malloc = function(size) {
_totalMemoryUsed += size;
var ptr = _defaultMalloc(size)
_mallocTracker[ptr] = size;
console.log("MALLOC'd @" + ptr + " " + size + " bytes -- TOTAL USED " + _totalMemoryUsed + " bytes");
return ptr;
}
Module._free = function(ptr) {
var size = _mallocTracker[ptr];
_totalMemoryUsed -= size;
console.log("FREE'd @" + ptr + " " + size + " bytes -- TOTAL USED " + _totalMemoryUsed + " bytes");
return _defaultFree(ptr);
}
解决方案
简短的回答:您尝试包装malloc
/free
不起作用,因为暴露Emscripten 的/实现的Module
对象不是本机 C++ 代码调用的入口点。但是,通过一些技巧,您可以通过多种方式跟踪这些调用。malloc()
free()
为什么你的覆盖不起作用
我认为您引用的答案可能更好地表述为:C++和调用的仿真在and中公开,但这些不是转换后的 C++ 代码调用的入口点。malloc()
free()
Module._malloc()
Module._free()
注意:我通常只会讨论malloc
这个答案的其余部分,但基本上适用于的所有内容malloc
也适用于free
.
我将把 Emscripten 如何处理的所有血腥细节留malloc()
到以后,但简而言之:
Emscripten 使用“标准设置”将 C++ 程序编译为
a.out.js
.该文件的很大一部分创建了一个
asm
对象。这包含所有转换后的 C++ 代码(例如 的 JavaScript 实现_main()
)和C++ 库函数的 JavaScript 版本(特别是_malloc()
)。转换后的 C++ 代码(在 内
asm
)直接引用内部库函数(也在 内asm
)。对 C++ 函数和许多库函数(特别是 和 )的引用
_main
作为_malloc
对象_free
的属性公开asm
。它们也作为对象的属性公开,Module
并作为独立变量存在。
因此,原始 C++ 代码只会调用代码块_malloc()
内定义的内部实现。asm
Emscripten 框架的其余部分以及任何附加的 JavaScript 代码也可以通过任何公开的引用调用此函数:_malloc
, Module._malloc
(or Module['_malloc']
) 和asm._malloc
(or asm['_malloc']
)。
因此,如果您替换任何或全部_malloc
,Module._malloc
或asm._malloc
使用“包装”版本,这只会影响从 Emscripten 框架的其余部分或其他 JavaScript 代码进行的调用。它不会影响从转换后的 C++ 代码进行的调用。
跟踪呼叫的方式_malloc()
/_free()
1.官方方式
在我们进入一些低级黑客之前,我应该提到 Emscripten 有一个内置的跟踪 API (根据他们的帮助页面)“提供一些有用的功能来更好地查看应用程序内部正在发生的事情,特别是关于到内存使用”。
我没有尝试使用它,但是对于认真的调试工作,这可能是要走的路。但是,它似乎需要一些“预先”的努力(您需要设置一个单独的进程来接收来自被测应用程序的跟踪消息),因此在某些情况下它可能是“矫枉过正”。
如果你想追求这个,官方文档可以在这里找到,这篇博文描述了一家公司如何利用 Tracing API 来发挥自己的优势(我没有从属关系:该页面刚刚出现在搜索结果中)。
2. 破解它
如上所述,问题在于转换后的 C++ 调用是对asm
对象内部函数的调用,因此不受我们可能在“外部”级别创建的任何包装器的影响。经过一番调查,我设计了两种方法来克服这个问题。由于两者都有点“hacky”,纯粹主义者可能想把目光移开……
首先,让我们从一小段代码开始作为我们的测试平台(改编自Emscripten 教程页面上的代码):
hello.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char* msg = malloc(1234321) ;
strcpy( msg, "Hello, world!" ) ;
printf( "%s\n", msg ) ;
free( msg ) ;
return 0;
}
注意:1234321
选择这个数字只是为了帮助搜索生成的 JavaScript 文件。这愉快地编译并按预期运行:
C:\Program Files\Emscripten\Test>emcc hello.c
C:\Program Files\Emscripten\Test>node a.out.js
Hello, world!
我们现在将创建以下 JavaScript 文件来“包装”malloc
和free
:
traceMalloc.js
Module={
'preRun': function() {
// Edit below or make an option to selectively wrap malloc/free.
if( true ) {
console.log( 'Wrapping malloc/free' ) ;
var real_malloc = _malloc ;
Module['_malloc'] = asm['_malloc'] = _malloc = function( size ) {
console.log( '_malloc( ' + size + ' )' ) ;
var result = real_malloc.apply( null, arguments ) ;
console.log( '<--- ' + result ) ;
return result ;
}
var real_free = _free ;
Module['_free'] = asm['_free'] = _free = function( ptr ) {
console.log( '_free( ' + ptr + ' )' ) ;
var result = real_free.apply( null, arguments ) ;
console.log( '<--- ' + result ) ;
return result ;
}
// Hack 2b: invoke semi-permanent code added to emscripten.py
//asm.wrapMallocFree(); }
}
}
这Module['preRun']
是一种让我们的代码在主入口点之前不久执行的方法。在函数内部,我们保存对“真实”_malloc
例程的引用,然后创建一个调用原始函数的新函数,并包装在跟踪消息中。新函数替换了对原始的所有三个“外部”引用_malloc
。
(现在,忽略底部附近的两条注释掉的行:它们将在以后使用)。
如果我们编译并运行它(使用--pre-js
选项告诉 Emscripten 将我们的 JavaScript 包含在输出a.out.js
文件中),正如 OP 发现的那样,我们只有有限的成功:
C:\Program Files\Emscripten\Test>emcc --pre-js traceMalloc.js hello.c
C:\Program Files\Emscripten\Test>node a.out.js
Wrapping malloc/free
_malloc( 42 )
<--- 5251080
_malloc( 5 )
<--- 5251128
Hello, world!
在 Emscripten 框架的某个地方有两个调用_malloc
,但我们感兴趣的一个 - 来自我们的 C 代码的一个 - 没有被跟踪。
2a. 一键破解
如果我们检查该a.out.js
文件,我们会发现以下代码片段,它是我们的 C 代码转换为 JavaScript 的开始:
function _main() {
var $0 = 0, $1 = 0, $2 = 0, $3 = 0, $4 = 0, $fred = 0, $vararg_buffer = 0, label = 0, sp = 0;
sp = STACKTOP;
STACKTOP = STACKTOP + 16|0; if ((STACKTOP|0) >= (STACK_MAX|0)) abort();
$vararg_buffer = sp;
$0 = 0;
$1 = (_malloc(1234321)|0);
问题是调用_malloc
引用了内部函数,而不是我们覆盖的函数。为了解决这个问题,我们可以编辑 a.out.js
在顶部添加以下两行_main()
:
function _main() {
_malloc = asm._malloc;
_free = asm._free;
这将替换内部属性_malloc
和对对象持有的公共_free
版本的引用(到目前为止,已被我们的“包装”版本替换)。尽管这看起来有点循环,但它确实有效(包装的版本已经存储了对真实函数的引用,所以他们仍然调用它,而不是我们刚刚覆盖的引用)。asm
malloc
如果我们现在重新运行a.out.js
文件(不重建):
C:\Program Files\Emscripten\Test>node a.out.js
Wrapping malloc/free
_malloc( 42 )
<--- 5251080
_malloc( 5 )
<--- 5251128
_malloc( 1234321 )
<--- 5251144
Hello, world!
_free( 5251144 )
<--- undefined
我们现在可以看到原始的 C 调用malloc
和free
正在被跟踪。虽然这很有效,并且很容易应用,但下次运行时更改将丢失,emcc
因此我们每次都必须重新应用修复程序。
2b。破解框架
无需a.out.js
每次都编辑生成的文件,而是可以在 Emscripten 框架中编辑一个文件的一小部分以获得只需要应用一次的“修复”。
警告
如果您采用这种方法,请保留要修改的文件的原始副本。此外,虽然我相信我建议的修改是安全的,但我没有对其进行测试,超出了这个答案所需的范围。谨慎使用!
有问题的文件emscripten\1.35.0\emscripten.py
不在主安装目录中(至少在 Windows 下)。大概路径的中间部分会随着 Emscripten 的不同版本而改变。需要进行两项更改,最好使用fc
命令的输出来显示:
C:\Program Files\Emscripten\emscripten\1.35.0>fc emscripten.py.original emscripten.py
Comparing files emscripten.py.original and EMSCRIPTEN.PY
***** emscripten.py.original
exports = []
for export in all_exported:
***** EMSCRIPTEN.PY
exports = []
all_exported.append('wrapMallocFree') <--- Add this line
for export in all_exported:
*****
***** emscripten.py.original
// EMSCRIPTEN_START_FUNCS
function stackAlloc(size) {
***** EMSCRIPTEN.PY
// EMSCRIPTEN_START_FUNCS
function wrapMallocFree() { <--- Add these lines
console.log( 'wrapMallocFree()' ) ; <--- Add these lines
_malloc = asm._malloc ; <--- Add these lines
_free = asm._free ; <--- Add these lines
} <--- Add these lines
function stackAlloc(size) {
*****
在我的副本中,第一个更改在第 680 行,第二个更改在第 964 行。第一个更改告诉框架wrapMallocFree
从asm
对象中导出函数;第二个更改定义了将要导出的函数。可以看出,这只是执行了与我们在第2a节中手动编辑的相同的两行(以及一个完全可选的跟踪行,以显示激活已经发生)。
要利用此更改,我们还需要取消注释对我们新函数的调用,traceMalloc.js
因此它显示为:
return result ;
}
// Hack 2b: invoke semi-permanent code added to emscripten.py
asm.wrapMallocFree(); }
}
}
现在,我们可以重新构建并重新运行代码并查看所有调用跟踪,而无需手动编辑a.out.js
:
C:\Program Files\Emscripten\Test>emcc --pre-js traceMalloc.js hello.c
C:\Program Files\Emscripten\Test>node a.out.js
Wrapping malloc/free
wrapMallocFree()
_malloc( 42 )
<--- 5251080
_malloc( 5 )
<--- 5251128
_malloc( 1234321 )
<--- 5251144
Hello, world!
_free( 5251144 )
<--- undefined
正如if( true ) ...
一点traceMalloc.js
建议的那样,我们可以将更改保留emscripten.py
在原处,并有选择地打开或关闭对malloc
and的跟踪free
。不使用时,唯一的作用是asm
导出一个wrapMallocFree
永远不会被调用的函数 ( )。从我可以看到该文件的其余部分,这不应该引起任何问题(没有其他人会知道它在那里)。即使您的 C/C++ 代码包含一个名为 的函数wrapMallocFree
,因为这些名称的前缀是下划线(main
变得_main
等),也不应该发生冲突。
显然,如果您切换到不同版本的 Emscripten,您将需要重新应用相同(或类似)的更改。
所有血腥细节
malloc
正如所承诺的, Emscripten 生成的代码内部发生的一些细节。
事情变得“不确定”
如上所述,生成的很大一部分a.out.js
(大约 60% 用于测试程序)由asm
对象的创建组成。这段代码由EMSCRIPTEN_START_ASM
and括起来EMSCRIPTEN_END_ASM
,在相当高的层次上看起来像:
// EMSCRIPTEN_START_ASM
var asm = (function(global, env, buffer) {
...
function _main() {
...
$1 = (_malloc(1234321)|0);
...
}
...
function _malloc($bytes) {
...
return ($mem$0|0);
}
...
return { ... _malloc: _malloc, ... };
})
// EMSCRIPTEN_END_ASM
(Module.asmGlobalArg, Module.asmLibraryArg, buffer);
该对象asm
是使用立即调用的函数表达式 (IIFE) 模式定义的。本质上,整个块定义了一个立即执行的匿名函数。执行该函数的结果就是分配给对象的结果asm
。此执行发生在遇到上述代码时。“IIFE”的要点是在该匿名函数中定义的变量/函数仅对该函数中的代码可见。所有“外部世界”看到的是该函数返回的任何内容(分配给asm
)。
我们感兴趣的是,我们看到了_main
(转换后的 C 代码)和_malloc
(Emscripten 的内存分配器实现)的定义。由于 JavaScript/IIFE 的工作方式,_main
当_malloc
执行_malloc
.
IIFE 的返回值是一个具有许多属性的对象。碰巧这个对象的属性名称恰好与匿名函数中的对象/函数的名称相同。虽然这可能看起来令人困惑,但没有任何歧义。返回的对象(分配给asm
)有一个名为 的属性_malloc
。该属性的值设置为等于内部对象的值_malloc
(函数的定义本质上创建了一个属性/对象,该属性/对象引用作为函数主体的“代码块”。这个引用可以像所有其他参考)。
的定义Module
构建后不久,我们有以下代码块:
var _free = Module["_free"] = asm["_free"];
var _main = Module["_main"] = asm["_main"];
var _i64Add = Module["_i64Add"] = asm["_i64Add"];
var _memset = Module["_memset"] = asm["_memset"];
var runPostSets = Module["runPostSets"] = asm["runPostSets"];
var _malloc = Module["_malloc"] = asm["_malloc"];
对于新创建对象的选定属性,asm
它做了两件事:(a)它Module
在第二个对象(那些属性。全局变量供 Emscripten 框架的其他部分使用;该对象供可能添加到 Emscripten 生成的代码中的其他 JavaScript 代码使用。asm
Module
条条大路通_malloc
此时,我们有以下内容:
在用于创建的匿名函数中定义了一段代码,
asm
它提供了 Emscripten 对 C/C++_malloc
函数的实现/模拟。此代码是“真正的 malloc”。应该注意的是,此代码“存在”或多或少独立于“引用”它的任何对象/属性(如果有的话)。IIFE 的一个内部对象被称为当前
_malloc
引用上述代码。原始 C/C++ 代码的调用将使用此对象的值进行。malloc()
该对象
asm
有一个名为的属性_malloc
,该属性当前也引用了上述代码块。该对象
Module
还有一个名为的属性,该属性_malloc
当前引用了上述代码块。有一个全局对象
_malloc
。不出所料,它还引用了上面的代码块。
此时,使用_malloc
(global-scope), Module._malloc
(or Module['_malloc']
, asm._malloc
or _malloc
(在用于构建的 IIFE 内asm
) 都将在同一个代码块中结束 - 的“真正”实现malloc()
。
当执行以下代码片段时(在function
上下文中):
var real_malloc = _malloc ;
Module['_malloc'] = asm['_malloc'] = _malloc = function( size ) {
console.log( '_malloc( ' + size + ' )' ) ;
var result = real_malloc.apply( null, arguments ) ;
console.log( '<--- ' + result ) ;
return result ;
}
然后发生了几件事:
(全局)对象的原始值的副本
_malloc
被制作(real_malloc
)。正如我们在上面看到的,它包含对实现malloc()
. 虽然这恰好与IIFE-internal object的值相同_malloc
,但两者之间没有联系。如果/当 IIFE-internal 的_malloc
值发生变化时,它不会影响real_malloc
.创建了一个新的(匿名)函数。
malloc()
它包含对(使用上面创建的对象)的“真实”实现的调用real_malloc
以及一些用于跟踪调用的日志消息。对这个新函数的引用存储在我们上面提到的三个“外部”对象中:(
_malloc
全局范围)Module._malloc
和asm._malloc
. IIFE 内部对象_malloc
仍然指向malloc()
.
我们现在处于 OP 的阶段:外部调用malloc()
(来自 Emscripten 框架或其他 JavaScript 代码)将通过“包装器”函数汇集并可以跟踪。从转换后的 C/C++ 代码(使用 IIFE 内部对象_malloc
)进行的调用仍被定向到“真实”实现并且不被跟踪。
在匿名 IIFE 函数的上下文中执行以下操作时:
_malloc = asm._malloc ;
然后(并且只有那时)IIFE 内部对象_malloc
才会被更改。当它被执行时,它的新值 ( asm._malloc
) 正在引用我们的“包装器”函数。此时“references-to-malloc”的所有四个变体都指向我们的“wrapper”函数。该函数仍然可以(通过变量real_malloc
)访问 so 现在的“真实”实现malloc()
,每当代码的任何部分调用malloc()
时,该调用都会通过我们的包装函数,因此可以跟踪调用。
推荐阅读
- reactjs - 我们可以使用字符串来引用/调用状态吗?
- r - 仅删除两个 facet_grid() X 标签之一
- printing - 打印条码示例 cl-s621 Citizen
- c++ - C++对union的一些误解
- java - SwingWorker 发布()无法正常工作
- javascript - 当模糊远离提交按钮时,如何将焦点设置在表单中的第一个输入上?
- r - R:同一组ggplot中的非重叠条
- tcp-keepalive - 在另一个程序打开的端口上启用 TCP keepalive
- javascript - 将多个 if 语句转换为三元 (JavaScript)
- python - Python 减号和加号函数颠倒了?