本帖最后由 Frhvjhhv 于 2022-10-3 11:25 编辑
承接上回,我们把wasm文件下载下来,然后执行wasm2c www.wasm -o www.c 命令得到 www.c 和www.h文件
然后把wabt工具包里面的wasm-rt.h,wasm-rt-impl.c,wasm-rt-impl.h文件也拿出来
打开visualstudio,新建c++控制台项目(或者dll均可,为了方便演示我就建立=控制台项目了),然后把 www.c 和www.h,wasm-rt.h,wasm-rt-impl.c,wasm-rt-impl.h拷贝进去
组成工程文件,在主文件中导入www.h头文件,如下图所示:
然后编译一下:
发现抱错了。其中抱错的原因是我们加载wasm的过程中有导入函数,这些导入函数被反编译成了导入符号
www.h中的符号,分为导入 (import) 符号,和导出(export)符号。js 和 wasm 交互时,import 符号是 js 提供给 wasm 使用的。而 export 符号是 wasm 提供给 js 使用的。
其中导入符号是未定义的。就是我们要自己参考js中的导入函数去在www.c文件中实现
[JavaScript] 纯文本查看 复制代码 function initSync(A) {
var i = {
wbg: {}
};
return i.wbg.__wbg_new_59cb74e423758ede = function() {
return addHeapObject(new Error)
}
,
i.wbg.__wbg_stack_558ba5917b466edd = function(A, i) {
var g = passStringToWasm0(getObject(i).stack, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc)
, i = WASM_VECTOR_LEN;
getInt32Memory0()[A / 4 + 1] = i,
getInt32Memory0()[A / 4 + 0] = g
}
,
i.wbg.__wbg_error_4bb6c2a97407129a = function(A, i) {
try {
console.error(getStringFromWasm0(A, i))
} finally {
wasm.__wbindgen_free(A, i)
}
}
,
i.wbg.__wbindgen_object_drop_ref = function(A) {
takeObject(A)
}
,
i.wbg.__wbindgen_throw = function(A, i) {
throw new Error(getStringFromWasm0(A, i))
}
,
i = loadSync(A, i).instance,
wasm = i.export
参考上面的js代码,
__wbg_new_59cb74e423758ede是维护一个栈结构,并将err元素入栈
_wbg_stack_558ba5917b466edd 中的getObject(i).stack是取出栈里面的err元素
其中throw error的直接赋值为NULL即可
[C] 纯文本查看 复制代码 void _558ba5917b466eddZ_vii(u32 a, u32 b);
u32(*Z_wbgZ___wbg_new_59cb74e423758edeZ_iv)(void) = NULL;
void (*Z_wbgZ___wbg_stack_558ba5917b466eddZ_vii)(u32, u32) = *_558ba5917b466eddZ_vii;
void (*Z_wbgZ___wbg_error_4bb6c2a97407129aZ_vii)(u32, u32) = NULL;
void (*Z_wbgZ___wbindgen_object_drop_refZ_vi)(u32) = NULL;
void (*Z_wbgZ___wbindgen_throwZ_vii)(u32, u32) = NULL;
其中_558ba5917b466eddZ_vii函数的定义如下:
[C++] 纯文本查看 复制代码
typedef u32* wbindgen_malloc(u32 w2c_p0);
typedef u32* wbindgen_realloc(u32 w2c_p0, u32 w2c_p1, u32 w2c_p2);
int WASM_VECTOR_LEN;
int passStringToWasm0(char* A, wbindgen_malloc i, wbindgen_realloc g) {
int B = strlen(A);
u32 Q = i(B);
int e = 0;
for (e; e < B; e++) {
int o = (int)e;
if (127 < o)
break;
w2c_memory.data[Q + e] = o;
}
u32 Qq = g(Q, B, e);
WASM_VECTOR_LEN = e;
return Qq;
}
void _558ba5917b466eddZ_vii(u32 a, u32 b)
{
int g = passStringToWasm0("error", w2c___wbindgen_malloc, w2c___wbindgen_realloc);
int i = WASM_VECTOR_LEN;
(w2c_memory.data)[a + 1] = i;
(w2c_memory).data[a + 0] = g;
}
u32(*Z_wbgZ___wbg_new_59cb74e423758edeZ_iv)(void) = NULL;
void (*Z_wbgZ___wbg_stack_558ba5917b466eddZ_vii)(u32, u32) = *_558ba5917b466eddZ_vii;
void (*Z_wbgZ___wbg_error_4bb6c2a97407129aZ_vii)(u32, u32) = NULL;
void (*Z_wbgZ___wbindgen_object_drop_refZ_vi)(u32) = NULL;
void (*Z_wbgZ___wbindgen_throwZ_vii)(u32, u32) = NULL;
其中w2c_memory.data,w2c___wbindgen_malloc,u32等等均在www.c文件中已经定义。
对于build_in符号不用管他,因为wasm2c是用gcc编译的所以我们也要用gcc来编译和调试(不要用vs,会报一大堆错)
此时已经把环境补好了,然后我们把主文件即包含main入口点的c文件引入www.h
编译一下:
[Asm] 纯文本查看 复制代码 gcc -o www ConsoleApplication5.c wasm-rt-impl.c
编译通过,表明环境已经补完
接下来我们来分析一下如何调用:
我们转到www.c文件:
可以看到两个静态全局变量static wasm_rt_memory_t w2c_memory; static wasm_rt_table_t w2c_T0; 其中 w2c_memory即相当于web里面WebAssembly.Memory,w2c_T0相当于 js 中的 WebAssembly.Table.
转到wasm_rt_memory_t这个结构体的定义可以看到:
[C++] 纯文本查看 复制代码 typedef struct {
/** The linear memory data, with a byte length of `size`. */
uint8_t* data;
/** The current and maximum page count for this Memory object. If there is no
* maximum, `max_pages` is 0xffffffffu (i.e. UINT32_MAX). */
uint32_t pages, max_pages;
/** The current size of the linear memory, in bytes. */
uint32_t size;
} wasm_rt_memory_t;
其中data就相当于WebAssembly.Memory.buffer的uint8array形式。wasm_rt_memory_t还定义了其线性内存空间的大小和页数
然后我们在转到整个wasm的入口点:
[C++] 纯文本查看 复制代码 void WASM_RT_ADD_PREFIX(init)(void) {
init_func_types();
init_globals();
init_memory();
init_table();
init_exports();
}
即调用上面这四个函数,即可实现wasm环境的初始化。这四个函数的定义均在www.c文件中。(本次直接调用即可。但是如果外部对wasm的初始化有导入函数,就要分析了,下面是对四个函数的分析)
init_func_types的作用是注册函数原型。
[C++] 纯文本查看 复制代码 static void init_globals(void) {
w2c_g0 = 1048576u;
}
init_globals初始化全局变量
[C] 纯文本查看 复制代码 static void init_memory(void) {
wasm_rt_allocate_memory((&w2c_memory), 17, 65536);
LOAD_DATA(w2c_memory, 1048576u, data_segment_data_0, 8009);
LOAD_DATA(w2c_memory, 1057704u, data_segment_data_1, 1);
}
init_memory及其重要。allocate_memory就是为wasm环境分配页数为17,最大大小为65536的内存空间
LOAD_DATA就是加载一些字符串或者常量向wasm的内存中。
其中data_segment_data_0的定义如下:
他就相当于wat 的 S 表达式形式的下面字符串常量
我们来看一下 load_data的定义:
[Asm] 纯文本查看 复制代码 static inline void load_data(void *dest, const void *src, size_t n) {
memcpy(dest, src, n);
}
#define LOAD_DATA(m, o, i, s) load_data(&(m.data[o]), i, s)
#define DEFINE_LOAD(name, t1, t2, t3)
其四个参数分别是w2c_memory,要写入w2c_memory.data处的内存地址(注意,不是真实的内存地址,而是相对与w2c_memory.data这个uint8_t*的地址,即相对地址,也可以理解为w2c_memory.data=[1,2,3,4,4,,5,5,5],a[5]就是w2c_memory.data相对地址5处的数据) ,要写入的数据,数据长度。最终调用memcpy来把数据拷贝到内存空间中。
init_table初始化函数指针数组。Table 可以看成函数指针的数组,数组的每一项存放着函数的签名和函数的地址。这个数组可以放任何不同类型的函数指针,有了函数签名这信息,在函数调用的时候就可动态检查参数是否对应。wasm 可以往 Table 中放函数,js 也可以往 Table 中放函数。放置好函数之后,就可以通过 Table 的索引去间接调用。于是 wasm 和 js,只需要知道 Table 的函数索引,没有必要使用函数指针。
然后我们可以看到导出函数init_exports的定义:
[C] 纯文本查看 复制代码 static void init_exports(void) {
/* export: 'memory' */
WASM_RT_ADD_PREFIX(Z_memory) = (&w2c_memory);
/* export: '__wbg_rsapublickeypair_free' */
WASM_RT_ADD_PREFIX(Z___wbg_rsapublickeypair_freeZ_vi) = (&w2c___wbg_rsapublickeypair_free);
/* export: 'rsapublickeypair_new' */
WASM_RT_ADD_PREFIX(Z_rsapublickeypair_newZ_iv) = (&w2c_rsapublickeypair_new);
/* export: 'rsapublickeypair_init' */
WASM_RT_ADD_PREFIX(Z_rsapublickeypair_initZ_vi) = (&w2c_rsapublickeypair_init);
/* export: 'rsapublickeypair_encode' */
WASM_RT_ADD_PREFIX(Z_rsapublickeypair_encodeZ_viiii) = (&w2c_rsapublickeypair_encode);
/* export: '__wbindgen_malloc' */
WASM_RT_ADD_PREFIX(Z___wbindgen_mallocZ_ii) = (&w2c___wbindgen_malloc);
/* export: '__wbindgen_realloc' */
WASM_RT_ADD_PREFIX(Z___wbindgen_reallocZ_iiii) = (&w2c___wbindgen_realloc);
/* export: '__wbindgen_free' */
WASM_RT_ADD_PREFIX(Z___wbindgen_freeZ_vii) = (&w2c___wbindgen_free);
这里面实现了在www.h头文件中申明的导出函数。 分析之后。我们开始调用:[C] 纯文本查看 复制代码 char gccc[1000];void Yyyyyyy() {
init_func_types();
init_globals();
init_memory();
init_table();
init_exports();
}char* Gou(u32* text, int length)
{
Yyyyyyy();}
首先在www.c文件中定义一个返回值为char*的函数返回加密的字符串。函数参数为待加密字符串,字符串长度,然后调用Yyyyyyy函数初始化wasm环境[C] 纯文本查看 复制代码 u32 aa = w2c_rsapublickeypair_new();//1176460
w2c_rsapublickeypair_init(aa);
u32 nei = w2c___wbindgen_malloc(length);//1176428
int le = 0;
for (le; le < length; le++)
{
w2c_memory.data[nei + le] = text[le];
}
//LOAD_DATA(w2c_memory, nei, text, length);
// memcpy(&w2c_memory, text, length);
w2c_rsapublickeypair_encode(8, aa, nei, length);
然后加密流程与js一样:rsapublickeypair_new初始化,rsapublickeypair_init初始化公钥。然后直接用w2c_memory.data[nei + le] = text[le];向wasm的内存空间中赋值。[C] 纯文本查看 复制代码 u32 bbb = i32_load(&w2c_memory, 8);
u32 bb = i32_load(&w2c_memory, 12);
#include <stdio.h>
printf("%d\n", bbb);
printf("%d\n", i32_load(&w2c_memory, 12));
// char* src;
//src= (char*)malloc((int)bb);
for (int i = 0; i < (int)bb; i++)
{
//printf("%c", w2c_memory.data[bbb + i]);
gccc[i] = w2c_memory.data[bbb + i];
// src[i] = (char)(w2c_memory.data[bbb + i]);
}
w2c___wbindgen_free(bbb, bb);
return gccc;
这里用i32_load(&w2c_memory, 12);直接取出内存处12的值。同样取出内存处为8的值,然后打印出这两个值然后取出内存处bbb,长度为bb的字符串即可。。在www.h头文件中导出函数 extern char* Gou(char*, int);然后在主程序mian函数中调用Gou函数:[C] 纯文本查看 复制代码
int main()
{
const int length = 12;
char* str = "666666890ftt";
u32 cc[12];
for (int i = 0; i < length; i++)
{
cc[i] = (u32)(str[i]);
}
//Gou(cc, 6);
// printf("%s\n", cc);
char* ccc;
//int ccc;
ccc = Gou(cc, length);
printf("%s", ccc);
//free(ccc);
getch();
}
我们输入字符串666666890ftt,返回加密结果
现在来编译一下:
[Asm] 纯文本查看 复制代码 gcc -o www ConsoleApplication5.c wasm-rt-impl.c www.c
编译成功,然后运行一下,返回了字符串fb32ca7c2863964f2438fa50a50d8e3f
返回了加密结果
打开之前爬取的html,输入加密字符串,验证一下:
正确。至此就结束了。我们可以写出c++ dll供其他语言调用。
参考:执行wasm转换出来的c代码https://zhuanlan.zhihu.com/p/43986042
|