如何在 javascript (emscripten) 中覆盖 c++ malloc/free?
How to override c++ malloc/free in javascript (emscripten)?
我覆盖了 Javascript(emscripten) 中的 Module._malloc 和 Module._free,方法是包装原始函数并添加 Console.log 以显示内存地址、大小和总分配内存。
我发现新函数仅捕获 Javascript 对 Module._malloc 和 Module._free 的调用,而不会捕获对 malloc() 和 free() 的 c++ 级调用。我想知道为什么。
根据Ofria先生在这里的回答,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
无效,因为 Module
对象 exposes Emscripten 的 malloc()
/free()
实现 而不是 本机 C++ 代码调用的 entry-points。但是,只要稍加技巧,您就可以 跟踪这些调用。
为什么您的覆盖不起作用
我认为您引用的答案措辞可能更好:C++ 的 malloc()
和 free()
调用的 仿真 在 [=31= 中公开] 和 Module._free()
,但这些 不是 转换后的 C++ 代码调用的 entry-points。
注意:我通常只会在这个答案的剩余部分谈论malloc
,但基本上适用于malloc
的所有内容也适用于free
.
我将把 Emscripten 如何处理 malloc()
的所有血腥细节留到以后,但简而言之:
使用"standard settings",Emscripten将C++程序编译成a.out.js
.
此文件的一大块内容创建了一个 asm
对象。这包含所有转换后的 C++ 代码(例如 _main()
的 JavaScript 实现) 和 JavaScript 版本的 C++ 库函数(特别是 _malloc()
).
转换后的 C++ 代码(在 asm
内)直接引用内部库函数(也在 asm
内)。
对 C++ 函数和许多库函数(特别是 _main
、_malloc
和 _free
)的引用作为 asm
对象。它们 也 作为 Module
对象的属性公开,并作为独立变量存在。
因此,原始 C++ 代码将 仅 调用 asm
代码块中定义的 _malloc()
的内部实现。 Emscripten 框架的其余部分,以及任何额外的 JavaScript 代码也可以通过任何公开的引用调用此函数:_malloc
、Module._malloc
(或 Module['_malloc']
)和 asm._malloc
(或asm['_malloc']
)。
因此,如果您将 _malloc
、Module._malloc
或 asm._malloc
中的任何一个或全部替换为 "wrapped" 版本,这只会影响来自其余版本的调用Emscripten 框架或额外的 JavaScript 代码。它将不会影响从转换后的 C++ 代码进行的调用。
跟踪调用到 _malloc()
/_free()
的方法
1。官方方式
在我们进入一些 low-level hackery 之前,我应该提到 Emscripten 有一个 Tracing API built-in 其中(根据他们的帮助页)“提供了一些有用的功能来更好地查看应用程序内部发生的事情,特别是在内存使用方面”。
我没有尝试使用它,但对于认真的调试工作,这可能是可行的方法。但是,它似乎需要一些 "up-front" 的努力(您需要设置一个单独的进程来接收来自被测应用程序的跟踪消息),因此在某些情况下可能 "overkill"。
如果你想继续这个,官方文档 can be found here and this blog post 描述了一家公司如何使用 Tracing API 来发挥他们的优势(我没有隶属关系:那个页面刚刚出现在搜索结果中) .
2。破解它
如上所述,问题在于转换后的 C++ 调用是针对 asm
对象中的内部函数,因此不受我们可能在 [=437= 中创建的任何包装器的影响] 等级。经过一些调查,我设计了两种方法来克服这个问题。由于两者都有点"hacky",纯粹主义者可能想把目光移开...
首先,让我们从一小段代码开始作为我们的 test-bed(改编自 Emscripten Tutorial 页面上的代码):
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文件。这愉快地编译并按预期 运行s:
C:\Program Files\Emscripten\Test>emcc hello.c
C:\Program Files\Emscripten\Test>node a.out.js
Hello, world!
我们现在将创建以下 JavaScript 文件到 "wrap" 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']
是一种让我们的代码在主 entry-point 之前执行的方法。在函数内部,我们保存对 "real" _malloc
例程的引用,然后创建一个调用原始函数的新函数,包装在 trace-messages 中。新函数替换了对原始 _malloc
.
的所有三个 "external" 引用
(暂时忽略底部附近的两行commented-out:稍后会用到它们)。
如果我们编译 运行 这个(使用 --pre-js
option 告诉 Emscripten 在输出 a.out.js
文件中包含我们的 JavaScript 片段),我们有,正如 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。 One-Shot黑客
如果我们检查在a.out.js
文件中,我们会发现下面的片段,这是我们的C代码开始转换为JavaScript:
function _main() {
var [=14=] = 0, = 0, = 0, = 0, = 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;
[=14=] = 0;
= (_malloc(1234321)|0);
问题是对 _malloc
的调用引用了 内部 函数,而不是我们重写的函数。要解决这个问题,我们可以 edit a.out.js
在 _main()
的顶部添加以下两行:
function _main() {
_malloc = asm._malloc;
_free = asm._free;
这会将内部属性 _malloc
和 _free
替换为对 asm
对象持有的 public 版本的引用(到目前为止,已经被我们的 "wrapped" 版本取代了)。虽然这看起来有点循环,但它确实有效(包装版本已经存储了对 real malloc
函数的引用,所以他们仍然调用它,而 不 我们刚刚覆盖的引用)。
如果我们现在re-runa.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
我们现在可以看到对 malloc
和 free
的原始 C 调用正在被跟踪。虽然这行得通并且易于应用,但下次我们 运行 emcc
时更改将丢失,因此我们每次都必须 re-apply 修复。
2b。破解框架
不是每次都编辑生成的 a.out.js
,而是可以在 Emscripten 框架中编辑一个文件的 small 部分以获得 "fix" 只需申请一次。
Warning
If you adopt this method keep an original copy of the file to be modified. Also, while I believe my suggested modification to be safe, I have not tested it beyond what was needed for this answer. Use with due caution!
有问题的文件位于主安装目录 emscripten.35.0\emscripten.py
之外(至少在 Windows 下)。想必路径的中间部分会随着不同版本的Emscripten而改变。需要进行两项更改,最好使用 fc
命令的输出显示:
C:\Program Files\Emscripten\emscripten.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 行。第一个更改告诉框架从 asm
对象导出函数 wrapMallocFree
;第二个更改定义了将要导出的函数。可以看出,这只是执行了与我们在 2a 部分手动编辑的相同的两行(以及完全可选的 trace-line,以显示激活已经发生)。 =166=]
要利用此更改,我们还需要 un-comment 调用 traceMalloc.js
中的新函数,因此它显示为:
return result ;
}
// Hack 2b: invoke semi-permanent code added to emscripten.py
asm.wrapMallocFree(); }
}
}
现在,我们可以re-build和re-run代码和看到所有跟踪的呼叫而无需手动编辑的 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
正如 traceMalloc.js
的 if( true ) ...
位所建议的那样,我们可以保留对 emscripten.py
的更改,并有选择地打开或关闭 malloc
和 [= 的跟踪25=]。不使用时,唯一的影响是 asm
导出一个永远不会被调用的函数 (wrapMallocFree
)。从我对该文件其余部分的了解来看,这应该不会造成任何问题(没有其他人会知道它在那里)。即使您的 C/C++ 代码包含一个名为 wrapMallocFree
的函数,因为这样的名称带有下划线前缀(main
变为 _main
等),也应该有没有冲突。
显然,如果您切换到不同版本的 Emscripten,则需要 re-apply 相同(或相似)的更改。
所有血淋淋的细节
正如承诺的那样,Emscripten 生成的代码中 malloc
发生的一些细节。
事情得到'iffy'
如上所述,生成的 a.out.js
中有很大一部分(测试程序约占 60%)包含 asm
对象的创建。此代码由 EMSCRIPTEN_START_ASM
和 EMSCRIPTEN_END_ASM
括起来,在相当高的级别上看起来像:
// EMSCRIPTEN_START_ASM
var asm = (function(global, env, buffer) {
...
function _main() {
...
= (_malloc(1234321)|0);
...
}
...
function _malloc($bytes) {
...
return ($mem[=20=]|0);
}
...
return { ... _malloc: _malloc, ... };
})
// EMSCRIPTEN_END_ASM
(Module.asmGlobalArg, Module.asmLibraryArg, buffer);
对象 asm
是使用 immediately invoked function expression (IIFE) pattern 定义的。本质上,整个块定义了一个立即执行的匿名函数。执行该函数的结果是分配给对象 asm
的结果。此执行发生在遇到上述代码时。 "IIFE" 的要点是 variables/functions 在 中定义了 匿名函数仅对该函数中的代码可见。 "outside world" 看到的是函数 returns(分配给 asm
)的任何内容。
我们感兴趣的是,我们看到了 _main
(转换后的 C 代码)和 _malloc
(Emscripten 的内存分配器实现)的定义。由于 JavaScript/IIFEs 的工作方式,当执行 _main
中的代码时,对 _malloc
的调用将始终引用 _malloc
.
的内部版本
IIFE 的return 值是一个具有多个属性的对象。碰巧这个对象的属性名称恰好与匿名函数中 objects/functions 的名称相同。虽然这可能看起来令人困惑,但不涉及歧义。 returned 对象(分配给 asm
)有一个名为 _malloc
的 属性。 属性 的 value 设置为等于内部对象 _malloc
的 value (函数的定义本质上是创建一个 property/object 来引用作为函数主体的 "block of code"。这个引用可以是 manipula像所有其他参考资料一样编辑)。
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"];
对于 newly-created asm
对象的 selected 属性,这会做两件事:(a)它在第二个对象 (Module
) 中创建属性,该对象引用与 asm
的 属性 相同的内容,并且 (b) 它创建一些也引用这些属性的全局变量。全局变量供 Emscripten 框架的其他部分使用; Module
对象供可能添加到 Emscripten-generated 代码的其他 JavaScript 代码使用。
条条大路通_malloc
此时,我们有以下内容:
在用于创建 asm
的匿名函数中定义了一段代码,它提供了 Emscripten 的 implementation/emulation 的 C/C++ 的 _malloc
函数。这个代码就是"real malloc"。需要注意的是,这段代码 "exists" more-or-less 独立于任何 objects/properties (如果有的话) "reference" 它。
IIFE 有一个名为 _malloc
的内部对象,当前 引用了上述代码。原始 C/C++ 代码对 malloc()
的调用将使用此对象的值进行。
对象 asm
有一个名为 _malloc
的 属性, 也 当前引用上述代码块。
对象 Module
也 有一个 属性 称为 _malloc
当前引用上述代码块。
有一个全局对象_malloc
。不出所料,它也引用了上面的代码块。
此时,使用_malloc
(global-scope)、Module._malloc
(或Module['_malloc']
、asm._malloc
或_malloc
(在用于构建 asm
) 的 IIFE 将 all 以相同的代码块结束 – "real" 实现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()
的 "real" 代码块的引用。虽然此 恰好与 IIFE-internal 对象 _malloc
具有相同的值,但两者之间没有任何联系。 If/whenIIFE-internal_malloc
的值改变了,不会影响real_malloc
的值。
创建了一个新的(匿名)函数。它包含对 malloc()
的 "real" 实现的调用(使用上面创建的对象 real_malloc
)以及一些 log-messages 来跟踪调用。
对这个新函数的引用存储在我们上面提到的三个 "outside" 对象中:_malloc
(global-scope)、Module._malloc
和 asm._malloc
。 IIFE-internal 对象 _malloc
仍然指向 malloc()
的 "real implementation"。
我们现在处于 OP 到达的阶段:外部 调用 malloc()
(由 Emscripten 框架或其他 JavaScript 代码生成) 将通过 "wrapper" 函数汇集并可被追踪。从转换后的 C/C++ 代码(使用 IIFE-internal 对象 _malloc
)进行的调用仍然指向 "real" 实现,不会被跟踪。
当在匿名 IIFE 函数的上下文中执行以下 :
_malloc = asm._malloc ;
然后(并且只有到那时)IIFE-internal 对象 _malloc
才会被更改。执行此操作时,它的新值 (asm._malloc
) 正在引用我们的 "wrapper" 函数。那时 "references-to-malloc" 的所有四个 变体都指向我们的 "wrapper" 函数。该函数仍然可以(通过变量 real_malloc
)访问 malloc()
的 "real" 实现,所以现在,每当 any 部分代码调用 malloc()
,该调用通过我们的包装函数,因此可以跟踪调用。
我覆盖了 Javascript(emscripten) 中的 Module._malloc 和 Module._free,方法是包装原始函数并添加 Console.log 以显示内存地址、大小和总分配内存。
我发现新函数仅捕获 Javascript 对 Module._malloc 和 Module._free 的调用,而不会捕获对 malloc() 和 free() 的 c++ 级调用。我想知道为什么。
根据Ofria先生在这里的回答,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
无效,因为 Module
对象 exposes Emscripten 的 malloc()
/free()
实现 而不是 本机 C++ 代码调用的 entry-points。但是,只要稍加技巧,您就可以 跟踪这些调用。
为什么您的覆盖不起作用
我认为您引用的答案措辞可能更好:C++ 的 malloc()
和 free()
调用的 仿真 在 [=31= 中公开] 和 Module._free()
,但这些 不是 转换后的 C++ 代码调用的 entry-points。
注意:我通常只会在这个答案的剩余部分谈论malloc
,但基本上适用于malloc
的所有内容也适用于free
.
我将把 Emscripten 如何处理 malloc()
的所有血腥细节留到以后,但简而言之:
使用"standard settings",Emscripten将C++程序编译成
a.out.js
.此文件的一大块内容创建了一个
asm
对象。这包含所有转换后的 C++ 代码(例如_main()
的 JavaScript 实现) 和 JavaScript 版本的 C++ 库函数(特别是_malloc()
).转换后的 C++ 代码(在
asm
内)直接引用内部库函数(也在asm
内)。对 C++ 函数和许多库函数(特别是
_main
、_malloc
和_free
)的引用作为asm
对象。它们 也 作为Module
对象的属性公开,并作为独立变量存在。
因此,原始 C++ 代码将 仅 调用 asm
代码块中定义的 _malloc()
的内部实现。 Emscripten 框架的其余部分,以及任何额外的 JavaScript 代码也可以通过任何公开的引用调用此函数:_malloc
、Module._malloc
(或 Module['_malloc']
)和 asm._malloc
(或asm['_malloc']
)。
因此,如果您将 _malloc
、Module._malloc
或 asm._malloc
中的任何一个或全部替换为 "wrapped" 版本,这只会影响来自其余版本的调用Emscripten 框架或额外的 JavaScript 代码。它将不会影响从转换后的 C++ 代码进行的调用。
跟踪调用到 _malloc()
/_free()
的方法
1。官方方式
在我们进入一些 low-level hackery 之前,我应该提到 Emscripten 有一个 Tracing API built-in 其中(根据他们的帮助页)“提供了一些有用的功能来更好地查看应用程序内部发生的事情,特别是在内存使用方面”。
我没有尝试使用它,但对于认真的调试工作,这可能是可行的方法。但是,它似乎需要一些 "up-front" 的努力(您需要设置一个单独的进程来接收来自被测应用程序的跟踪消息),因此在某些情况下可能 "overkill"。
如果你想继续这个,官方文档 can be found here and this blog post 描述了一家公司如何使用 Tracing API 来发挥他们的优势(我没有隶属关系:那个页面刚刚出现在搜索结果中) .
2。破解它
如上所述,问题在于转换后的 C++ 调用是针对 asm
对象中的内部函数,因此不受我们可能在 [=437= 中创建的任何包装器的影响] 等级。经过一些调查,我设计了两种方法来克服这个问题。由于两者都有点"hacky",纯粹主义者可能想把目光移开...
首先,让我们从一小段代码开始作为我们的 test-bed(改编自 Emscripten Tutorial 页面上的代码):
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文件。这愉快地编译并按预期 运行s:
C:\Program Files\Emscripten\Test>emcc hello.c
C:\Program Files\Emscripten\Test>node a.out.js
Hello, world!
我们现在将创建以下 JavaScript 文件到 "wrap" 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']
是一种让我们的代码在主 entry-point 之前执行的方法。在函数内部,我们保存对 "real" _malloc
例程的引用,然后创建一个调用原始函数的新函数,包装在 trace-messages 中。新函数替换了对原始 _malloc
.
(暂时忽略底部附近的两行commented-out:稍后会用到它们)。
如果我们编译 运行 这个(使用 --pre-js
option 告诉 Emscripten 在输出 a.out.js
文件中包含我们的 JavaScript 片段),我们有,正如 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。 One-Shot黑客
如果我们检查在a.out.js
文件中,我们会发现下面的片段,这是我们的C代码开始转换为JavaScript:
function _main() {
var [=14=] = 0, = 0, = 0, = 0, = 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;
[=14=] = 0;
= (_malloc(1234321)|0);
问题是对 _malloc
的调用引用了 内部 函数,而不是我们重写的函数。要解决这个问题,我们可以 edit a.out.js
在 _main()
的顶部添加以下两行:
function _main() {
_malloc = asm._malloc;
_free = asm._free;
这会将内部属性 _malloc
和 _free
替换为对 asm
对象持有的 public 版本的引用(到目前为止,已经被我们的 "wrapped" 版本取代了)。虽然这看起来有点循环,但它确实有效(包装版本已经存储了对 real malloc
函数的引用,所以他们仍然调用它,而 不 我们刚刚覆盖的引用)。
如果我们现在re-runa.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
我们现在可以看到对 malloc
和 free
的原始 C 调用正在被跟踪。虽然这行得通并且易于应用,但下次我们 运行 emcc
时更改将丢失,因此我们每次都必须 re-apply 修复。
2b。破解框架
不是每次都编辑生成的 a.out.js
,而是可以在 Emscripten 框架中编辑一个文件的 small 部分以获得 "fix" 只需申请一次。
Warning
If you adopt this method keep an original copy of the file to be modified. Also, while I believe my suggested modification to be safe, I have not tested it beyond what was needed for this answer. Use with due caution!
有问题的文件位于主安装目录 emscripten.35.0\emscripten.py
之外(至少在 Windows 下)。想必路径的中间部分会随着不同版本的Emscripten而改变。需要进行两项更改,最好使用 fc
命令的输出显示:
C:\Program Files\Emscripten\emscripten.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 行。第一个更改告诉框架从 asm
对象导出函数 wrapMallocFree
;第二个更改定义了将要导出的函数。可以看出,这只是执行了与我们在 2a 部分手动编辑的相同的两行(以及完全可选的 trace-line,以显示激活已经发生)。 =166=]
要利用此更改,我们还需要 un-comment 调用 traceMalloc.js
中的新函数,因此它显示为:
return result ;
}
// Hack 2b: invoke semi-permanent code added to emscripten.py
asm.wrapMallocFree(); }
}
}
现在,我们可以re-build和re-run代码和看到所有跟踪的呼叫而无需手动编辑的 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
正如 traceMalloc.js
的 if( true ) ...
位所建议的那样,我们可以保留对 emscripten.py
的更改,并有选择地打开或关闭 malloc
和 [= 的跟踪25=]。不使用时,唯一的影响是 asm
导出一个永远不会被调用的函数 (wrapMallocFree
)。从我对该文件其余部分的了解来看,这应该不会造成任何问题(没有其他人会知道它在那里)。即使您的 C/C++ 代码包含一个名为 wrapMallocFree
的函数,因为这样的名称带有下划线前缀(main
变为 _main
等),也应该有没有冲突。
显然,如果您切换到不同版本的 Emscripten,则需要 re-apply 相同(或相似)的更改。
所有血淋淋的细节
正如承诺的那样,Emscripten 生成的代码中 malloc
发生的一些细节。
事情得到'iffy'
如上所述,生成的 a.out.js
中有很大一部分(测试程序约占 60%)包含 asm
对象的创建。此代码由 EMSCRIPTEN_START_ASM
和 EMSCRIPTEN_END_ASM
括起来,在相当高的级别上看起来像:
// EMSCRIPTEN_START_ASM
var asm = (function(global, env, buffer) {
...
function _main() {
...
= (_malloc(1234321)|0);
...
}
...
function _malloc($bytes) {
...
return ($mem[=20=]|0);
}
...
return { ... _malloc: _malloc, ... };
})
// EMSCRIPTEN_END_ASM
(Module.asmGlobalArg, Module.asmLibraryArg, buffer);
对象 asm
是使用 immediately invoked function expression (IIFE) pattern 定义的。本质上,整个块定义了一个立即执行的匿名函数。执行该函数的结果是分配给对象 asm
的结果。此执行发生在遇到上述代码时。 "IIFE" 的要点是 variables/functions 在 中定义了 匿名函数仅对该函数中的代码可见。 "outside world" 看到的是函数 returns(分配给 asm
)的任何内容。
我们感兴趣的是,我们看到了 _main
(转换后的 C 代码)和 _malloc
(Emscripten 的内存分配器实现)的定义。由于 JavaScript/IIFEs 的工作方式,当执行 _main
中的代码时,对 _malloc
的调用将始终引用 _malloc
.
IIFE 的return 值是一个具有多个属性的对象。碰巧这个对象的属性名称恰好与匿名函数中 objects/functions 的名称相同。虽然这可能看起来令人困惑,但不涉及歧义。 returned 对象(分配给 asm
)有一个名为 _malloc
的 属性。 属性 的 value 设置为等于内部对象 _malloc
的 value (函数的定义本质上是创建一个 property/object 来引用作为函数主体的 "block of code"。这个引用可以是 manipula像所有其他参考资料一样编辑)。
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"];
对于 newly-created asm
对象的 selected 属性,这会做两件事:(a)它在第二个对象 (Module
) 中创建属性,该对象引用与 asm
的 属性 相同的内容,并且 (b) 它创建一些也引用这些属性的全局变量。全局变量供 Emscripten 框架的其他部分使用; Module
对象供可能添加到 Emscripten-generated 代码的其他 JavaScript 代码使用。
条条大路通_malloc
此时,我们有以下内容:
在用于创建
asm
的匿名函数中定义了一段代码,它提供了 Emscripten 的 implementation/emulation 的 C/C++ 的_malloc
函数。这个代码就是"real malloc"。需要注意的是,这段代码 "exists" more-or-less 独立于任何 objects/properties (如果有的话) "reference" 它。IIFE 有一个名为
_malloc
的内部对象,当前 引用了上述代码。原始 C/C++ 代码对malloc()
的调用将使用此对象的值进行。对象
asm
有一个名为_malloc
的 属性, 也 当前引用上述代码块。对象
Module
也 有一个 属性 称为_malloc
当前引用上述代码块。有一个全局对象
_malloc
。不出所料,它也引用了上面的代码块。
此时,使用_malloc
(global-scope)、Module._malloc
(或Module['_malloc']
、asm._malloc
或_malloc
(在用于构建 asm
) 的 IIFE 将 all 以相同的代码块结束 – "real" 实现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()
的 "real" 代码块的引用。虽然此 恰好与 IIFE-internal 对象_malloc
具有相同的值,但两者之间没有任何联系。 If/whenIIFE-internal_malloc
的值改变了,不会影响real_malloc
的值。创建了一个新的(匿名)函数。它包含对
malloc()
的 "real" 实现的调用(使用上面创建的对象real_malloc
)以及一些 log-messages 来跟踪调用。对这个新函数的引用存储在我们上面提到的三个 "outside" 对象中:
_malloc
(global-scope)、Module._malloc
和asm._malloc
。 IIFE-internal 对象_malloc
仍然指向malloc()
的 "real implementation"。
我们现在处于 OP 到达的阶段:外部 调用 malloc()
(由 Emscripten 框架或其他 JavaScript 代码生成) 将通过 "wrapper" 函数汇集并可被追踪。从转换后的 C/C++ 代码(使用 IIFE-internal 对象 _malloc
)进行的调用仍然指向 "real" 实现,不会被跟踪。
当在匿名 IIFE 函数的上下文中执行以下 :
_malloc = asm._malloc ;
然后(并且只有到那时)IIFE-internal 对象 _malloc
才会被更改。执行此操作时,它的新值 (asm._malloc
) 正在引用我们的 "wrapper" 函数。那时 "references-to-malloc" 的所有四个 变体都指向我们的 "wrapper" 函数。该函数仍然可以(通过变量 real_malloc
)访问 malloc()
的 "real" 实现,所以现在,每当 any 部分代码调用 malloc()
,该调用通过我们的包装函数,因此可以跟踪调用。