我通过包装原始函数并仅添加 Console.log 来显示内存地址、大小和分配的总内存,从而覆盖 Javascript(emscripten) 中的 Module._malloc 和 Module._free。
我发现新函数仅捕获对 Module._malloc 和 Module._free 的 Javascript 调用,并且不能捕获对 malloc() 和 free() 的 C++ 级别调用。我想知道为什么。
根据Ofria先生的回答/sf/answers/2384014391/,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);
}
Run Code Online (Sandbox Code Playgroud)
简短的回答:您尝试包装malloc/free不起作用,因为公开Emscripten 的/实现的Module对象不是本机 C++ 代码调用的入口点。然而,通过一些技巧,您可以通过一些方法来追踪这些调用。malloc()free()
我认为您引用的答案可能更好地表述为:C++ 的模拟malloc()和调用在andfree()中公开,但这些不是转换后的 C++ 代码调用的入口点。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()内定义的内部实现。asmEmscripten 框架的其余部分以及任何其他 JavaScript 代码也可以通过任何公开的引用调用此函数:_malloc、Module._malloc(或Module[\'_malloc\']) 和asm._malloc(或asm[\'_malloc\'])。
_malloc因此,如果您将 或 的任何或全部替换为“包装”版本,则这只会影响从 Emscripten 框架的其余部分或其他 JavaScript 代码进行的调用Module._malloc。asm._malloc它不会影响从转换后的 C++ 代码进行的调用。
_malloc()/的方法_free()在我们讨论一些低级黑客之前,我应该提到 Emscripten 有一个内置的跟踪 API (根据他们的帮助页面)“提供了一些有用的功能,可以更好地了解应用程序内部发生的情况,特别是在方面内存使用情况”。
\n\n我没有尝试使用它,但对于认真的调试工作,这可能是正确的方法。然而,它似乎需要一些“预先”的努力(您需要设置一个单独的进程来接收来自被测应用程序的跟踪消息),因此在某些情况下它可能是“矫枉过正”。
\n\n如果您想了解这一点,可以在此处找到官方文档,这篇博文描述了一家公司如何使用 Tracing API 来发挥其优势(我没有隶属关系:该页面刚刚出现在搜索结果中)。
\n\n如上所述,问题在于转换后的 C++ 调用所进行的调用是对象内的内部函数asm,因此不会受到我们可能在“外部”级别创建的任何包装器的影响。经过一番调查,我设计了两种方法来解决这个问题。由于两者都有点“hacky”,纯粹主义者可能想把目光移开……
首先,让我们从一小段代码开始作为我们的测试平台(改编自Emscripten 教程页面上的代码):
\n\nhello.c
#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\nint main() {\n char* msg = malloc(1234321) ;\n strcpy( msg, "Hello, world!" ) ;\n printf( "%s\\n", msg ) ;\n free( msg ) ;\n return 0;\n}\nRun Code Online (Sandbox Code Playgroud)\n\n注意1234321:选择该数字只是为了帮助搜索生成的 JavaScript 文件。这很高兴地按预期编译并运行:
C:\\Program Files\\Emscripten\\Test>emcc hello.c\n\nC:\\Program Files\\Emscripten\\Test>node a.out.js\nHello, world!\nRun Code Online (Sandbox Code Playgroud)\n\n我们现在将创建以下 JavaScript 文件来“包装”malloc和free:
traceMalloc.js
Module={\n \'preRun\': function() {\n // Edit below or make an option to selectively wrap malloc/free.\n if( true ) {\n console.log( \'Wrapping malloc/free\' ) ;\n var real_malloc = _malloc ;\n Module[\'_malloc\'] = asm[\'_malloc\'] = _malloc = function( size ) {\n console.log( \'_malloc( \' + size + \' )\' ) ;\n var result = real_malloc.apply( null, arguments ) ;\n console.log( \'<--- \' + result ) ;\n return result ;\n }\n var real_free = _free ;\n Module[\'_free\'] = asm[\'_free\'] = _free = function( ptr ) {\n console.log( \'_free( \' + ptr + \' )\' ) ;\n var result = real_free.apply( null, arguments ) ;\n console.log( \'<--- \' + result ) ;\n return result ;\n }\n // Hack 2b: invoke semi-permanent code added to emscripten.py\n //asm.wrapMallocFree(); }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n这Module[\'preRun\']是一种让我们的代码在主入口点之前不久执行的方法。在函数内部,我们保存对“真实”_malloc例程的引用,然后创建一个新函数来调用原始函数,并封装在跟踪消息中。新函数替换了对原始_malloc.
(现在,忽略底部附近的两行注释掉的行:稍后将使用它们)。
\n\n如果我们编译并运行它(使用--pre-js选项告诉 Emscripten 将我们的 JavaScript 片段包含在输出a.out.js文件中),正如 OP 所发现的那样,我们只取得了有限的成功:
C:\\Program Files\\Emscripten\\Test>emcc --pre-js traceMalloc.js hello.c\n\nC:\\Program Files\\Emscripten\\Test>node a.out.js\nWrapping malloc/free\n_malloc( 42 )\n<--- 5251080\n_malloc( 5 )\n<--- 5251128\nHello, world!\nRun Code Online (Sandbox Code Playgroud)\n\nEmscripten 框架中的某个位置有两个调用_malloc,但我们感兴趣的一个调用是 \xe2\x80\x93,而来自我们的 C 代码 \xe2\x80\x93 的调用尚未被追踪到。
如果我们检查该a.out.js文件,我们会发现以下代码片段,这是转换为 JavaScript 的 C 代码的开头:
function _main() {\n var $0 = 0, $1 = 0, $2 = 0, $3 = 0, $4 = 0, $fred = 0, $vararg_buffer = 0, label = 0, sp = 0;\n sp = STACKTOP;\n STACKTOP = STACKTOP + 16|0; if ((STACKTOP|0) >= (STACK_MAX|0)) abort();\n $vararg_buffer = sp;\n $0 = 0;\n $1 = (_malloc(1234321)|0);\nRun Code Online (Sandbox Code Playgroud)\n\n问题是对内部_malloc函数的调用引用了内部函数,而不是我们重写的函数。要解决此问题,我们可以编辑以在顶部添加以下两行: a.out.js_main()
function _main() {\n _malloc = asm._malloc;\n _free = asm._free;\nRun Code Online (Sandbox Code Playgroud)\n\n这替换了内部属性_malloc以及对对象持有的公共版本的_free引用(到目前为止,它们已被我们的“包装”版本替换)。虽然这看起来有点循环,但它是有效的(包装版本已经存储了对真实函数的引用,因此它们仍然调用它,而不是我们刚刚覆盖的引用)。asm malloc
如果我们现在重新运行该a.out.js文件(不重建):
C:\\Program Files\\Emscripten\\Test>node a.out.js\nWrapping malloc/free\n_malloc( 42 )\n<--- 5251080\n_malloc( 5 )\n<--- 5251128\n_malloc( 1234321 )\n<--- 5251144\nHello, world!\n_free( 5251144 )\n<--- undefined\nRun Code Online (Sandbox Code Playgroud)\n\n我们现在可以看到原始的 C 调用malloc和free正在被跟踪。虽然这有效并且很容易应用,但下次运行时更改将丢失,emcc因此我们每次都必须重新应用修复。
无需a.out.js每次都编辑生成的文件,而是可以在 Emscripten 框架中编辑一个文件的一小部分,以获得只需应用一次的“修复”。
\n\n\n警告
\n\n如果采用此方法,请保留要修改的文件的原始副本。另外,虽然我相信我建议的修改是安全的,但我还没有对其进行超出此答案所需的测试。请谨慎使用!
\n
有问题的文件不在emscripten\\1.35.0\\emscripten.py主安装目录中(至少在 Windows 下)。想必路径的中间部分会随着Emscripten版本的不同而改变。需要进行两项更改,最好使用fc命令的输出来显示:
C:\\Program Files\\Emscripten\\emscripten\\1.35.0>fc emscripten.py.original emscripten.py\nComparing files emscripten.py.original and EMSCRIPTEN.PY\n***** emscripten.py.original\n exports = []\n for export in all_exported:\n***** EMSCRIPTEN.PY\n exports = []\n all_exported.append(\'wrapMallocFree\') <--- Add this line\n for export in all_exported:\n*****\n\n***** emscripten.py.original\n// EMSCRIPTEN_START_FUNCS\nfunction stackAlloc(size) {\n***** EMSCRIPTEN.PY\n// EMSCRIPTEN_START_FUNCS\nfunction wrapMallocFree() { <--- Add these lines\n console.log( \'wrapMallocFree()\' ) ; <--- Add these lines\n _malloc = asm._malloc ; <--- Add these lines\n _free = asm._free ; <--- Add these lines\n} <--- Add these lines\nfunction stackAlloc(size) {\n*****\nRun Code Online (Sandbox Code Playgroud)\n\n在我的副本中,第一个更改位于第 680 行,第二个更改位于第 964 行。第一个更改告诉框架从对象导出函数;第二个更改告诉框架wrapMallocFree从对象中导出函数asm。第二个更改定义了将导出的函数。可以看出,这只是执行与我们在第2a节中手动编辑的相同的两行(以及完全可选的跟踪行,以显示激活已经发生)。
要利用此更改,我们还需要取消对新函数的调用的注释,如下traceMalloc.js所示:
return result ;\n }\n // Hack 2b: invoke semi-permanent code added to emscripten.py\n asm.wrapMallocFree(); }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n现在,我们可以重新构建并重新运行代码并查看跟踪的所有调用,而无需手动编辑a.out.js:
C:\\Program Files\\Emscripten\\Test>emcc --pre-js traceMalloc.js hello.c\n\nC:\\Program Files\\Emscripten\\Test>node a.out.js\nWrapping malloc/free\nwrapMallocFree()\n_malloc( 42 )\n<--- 5251080\n_malloc( 5 )\n<--- 5251128\n_malloc( 1234321 )\n<--- 5251144\nHello, world!\n_free( 5251144 )\n<--- undefined\nRun Code Online (Sandbox Code Playgroud)\n\n正如 的if( true ) ...建议traceMalloc.js,我们可以将更改保留emscripten.py在适当的位置,并有选择地打开或关闭malloc和的跟踪free。不使用时,唯一的效果是asm导出一个wrapMallocFree永远不会被调用的函数 ( )。从我对该文件其余部分的了解来看,这不会导致任何问题(其他任何东西都不会知道它在那里)。即使您的 C/C++ 代码包含一个名为 的函数wrapMallocFree,因为此类名称带有下划线前缀(main变为_main等),也不应该发生冲突。
显然,如果您切换到不同版本的 Emscripten,则需要重新应用相同(或类似)的更改。
\n\nmalloc正如所承诺的, Emscripten 生成的代码内部发生的一些细节。
如上所述,生成的很大一部分a.out.js(测试程序中大约 60%)由asm对象的创建组成。该代码由EMSCRIPTEN_START_ASM和括起来EMSCRIPTEN_END_ASM,在相当高的级别上看起来像:
// EMSCRIPTEN_START_ASM\nvar asm = (function(global, env, buffer) {\n\n ...\n\n function _main() {\n ...\n $1 = (_malloc(1234321)|0);\n ...\n }\n\n ...\n\n function _malloc($bytes) {\n ...\n return ($mem$0|0);\n }\n\n ...\n\n return { ... _malloc: _malloc, ... };\n})\n// EMSCRIPTEN_END_ASM\n(Module.asmGlobalArg, Module.asmLibraryArg, buffer);\nRun Code Online (Sandbox Code Playgroud)\n\n该对象asm是使用立即调用函数表达式 (IIFE) 模式定义的。本质上,整个块定义了一个立即执行的匿名函数。执行该函数的结果就是分配给对象的结果asm。该执行发生在遇到上述代码时。“IIFE”的要点是匿名函数中定义的变量/函数仅对该函数中的代码可见。所有“外部世界”看到的都是该函数返回的内容(分配给asm)。
我们感兴趣的是,我们看到了_main(转换后的 C 代码)和_malloc(Emscripten 的内存分配器实现)的定义。由于 JavaScript/IIFE 的工作方式,在执行 中的代码时_main,它的调用_malloc将始终引用_malloc.
IIFE 的返回值是一个具有许多属性的对象。碰巧该对象的属性名称恰好与匿名函数中的对象/函数名称相同。虽然这可能看起来令人困惑,但并不存在歧义。返回的对象(分配给asm)有一个名为 的属性_malloc。该属性的值设置为等于内部对象的值_malloc(函数的定义本质上创建一个属性/对象,该属性/对象引用作为函数主体的“代码块”。可以像所有操作一样操作该引用其他参考)。
Module构建后不久,我们有以下代码块:
\n\nvar _free = Module["_free"] = asm["_free"];\nvar _main = Module["_main"] = asm["_main"];\nvar _i64Add = Module["_i64Add"] = asm["_i64Add"];\nvar _memset = Module["_memset"] = asm["_memset"];\nvar runPostSets = Module["runPostSets"] = asm["runPostSets"];\nvar _malloc = Module["_malloc"] = asm["_malloc"];\nRun Code Online (Sandbox Code Playgroud)\n\n对于新创建的对象的选定asm属性,这会执行两件事:(a)在第二个对象 ( ) 中创建属性Module,该属性引用与 does 的属性相同的内容asm,以及(b)创建一些全局变量,这些变量也引用这些属性。全局变量供 Emscripten 框架的其他部分使用;该Module对象供可能添加到 Emscripten 生成的代码中的其他 JavaScript 代码使用。
_malloc此时,我们有以下内容:
\n\n在用于创建的匿名函数中定义了一个代码块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._mallocor _malloc(在用于构建 的 IIFE 内asm)都将最终得到同一代码块 \xe2\x80\x93 的“真正”实现malloc()。
当执行以下代码片段时(在上下文中function):
var real_malloc = _malloc ;\n Module[\'_malloc\'] = asm[\'_malloc\'] = _malloc = function( size ) {\n console.log( \'_malloc( \' + size + \' )\' ) ;\n var result = real_malloc.apply( null, arguments ) ;\n console.log( \'<--- \' + result ) ;\n return result ;\n }\nRun Code Online (Sandbox Code Playgroud)\n\n然后发生了几件事:
\n\n_malloc生成(全局)对象的原始值的副本( real_malloc)。正如我们在上面看到的,它保存了对实现malloc(). 虽然这恰好与 IIFE 内部对象的值相同_malloc,但两者之间没有任何联系。如果/当 IIFE-internal 的值_malloc发生更改时,它不会影响中保存的值real_malloc。
创建一个新的(匿名)函数。它包含对“真实”实现的调用malloc()(使用上面创建的对象real_malloc)以及一些跟踪调用的日志消息。
对这个新函数的引用存储在我们上面提到的三个“外部”对象中:_malloc(global-scope)Module._malloc和asm._malloc. IIFE 内部对象_malloc仍然指向 的“实际实现” malloc()。
我们现在正处于 OP 所处的阶段:外部调用malloc()(由 Emscripten 框架或其他 JavaScript 代码进行)将通过“包装器”函数进行传递,并且可以被追踪。从使用 IIFE 内部对象 \xe2\x80\x93 的转换后的 C/C++ 代码 \ _mallocxe2\x80\x93 进行的调用仍然定向到“真实”实现并且不会被跟踪。
当在匿名 IIFE 函数的上下文中执行以下操作时:
\n\n_malloc = asm._malloc ;\nRun Code Online (Sandbox Code Playgroud)\n\n然后(并且只有那时)IIFE 内部对象才会_malloc被更改。当执行时,它的新值 ( asm._malloc) 正在引用我们的“包装器”函数。此时, “references-to-malloc”的所有四个变体都指向我们的“包装器”函数。该函数仍然可以(通过变量real_malloc)访问 so 现在的“真实”实现malloc(),每当代码的任何malloc()部分调用 时,该调用都会通过我们的包装函数,以便可以跟踪该调用。
| 归档时间: |
|
| 查看次数: |
1959 次 |
| 最近记录: |