1.【开源项目】轻量元数据管理解决方案——Marquez
2.nodejs最新京东m端h5st 4.2签名算法4.2版本逆向,京东API接口,京东商品数据采集
3.Debug版本和Release版本有什么区别?
4.如何在C语言中嵌入汇编?
【开源项目】轻量元数据管理解决方案——Marquez
轻量级元数据管理解决方案——Marquez
Marquez,由WeWork开源的元数据管理工具,专为简化数据生态系统元数据的收集、聚合和可视化而设计。mvc框架web源码它提供了一个轻量级的元数据服务,帮助用户全面掌握数据集的产生和消费情况,以及数据处理过程的可视化,并集中管理数据集的生命周期。
Marquez在持续发展中,当前标星数为1.5K,最新版本发布于三周前的0..1,主要使用Java和TS语言开发。部署方式与Java项目类似,只需启动对应Web端服务和API服务。Marquez的血缘API简洁高效,便于建立数据血缘依赖关系,确保数据分析质量。如需获取安装包、源代码及学习资料,可访问官网或使用大数据流动后台回复“Marquez”。
Marquez的安装流程简洁,通过命令行即可快速完成。启动命令如下:$ git clone github.com/MarquezProject/marquez && cd marquez$ ./docker/up.sh --seed,之后通过访问/OpenLineage/...", "schemaURL": "openlineage.io/spec/1-0..." }' 完成任务后,使用类似代码进行:$ curl -X POST /OpenLineage/...", "_schemaURL": "github.com/OpenLineage/...", "fields": [ { "name": "a", "type": "VARCHAR"}, { "name": "b", "type": "VARCHAR"} ] } } }], "producer": "github.com/OpenLineage/...", "schemaURL": "openlineage.io/spec/1-0..." }' 正常运行应接收到 CREATED的响应,并在页面上找到血缘展示。
Marquez不仅简化了元数据管理,还提供了标准的元数据采集方案,目前支持Spark、Airflow的表级别和列级别数据血缘收集,而Flink仅支持表级别的血缘收集。Marquez未来有望支持更多数据源,共同期待其发展。
nodejs最新京东m端h5st 4.2签名算法4.2版本逆向,京东API接口,京东商品数据采集
分析京东m端使用的新版本h5st 4.2签名算法,与之前的4.1版本相比,算法在混淆window、JD等环境变量方面显著提高,增加了逆向难度。本文详细解析4.2版本的逆向过程,帮助读者了解其加密机制。
在逆向研究中,关注的是4.2版本签名算法的加密逻辑。为了实现对京东API接口的访问,需要解码并理解其中的加密过程。具体而言,重点关注评论接口和参数h5st。
对于参数h5st,其加密结果为%3Biwtagp9mzt%3Be%3BtkwaacblMyszeDMrMjMz4egDE8H9pUcx3gZF-xLwr2oOECX4cd8O4rqH_H1v1EJsrbFkhTR1r9ID2kf_%3B6a1e6cedbaaebaeabcddfacce4c%3B4.2%3B%3B0aeefafc5a7faa1ad5ecfdaad5fe7e4aacccbbcedaa6faacdaec2fdcd9cfadabecbfd6c8dcaacaeb2acc2f9dee2fcdac8faacdbaddfcccbedeccedbffc1d8fddad2bafbdb7accaec0beb7a1bbdc9afcecee4efddabbfbfdafd5be6fd3afbfec6dd0bfdbf6acba2e4fceacdeae4abffeddfc1b8cbace,版本号显示为4.2。
在解码body参数后,发现其加密位置与h5st 4.1版本的加密逻辑相似。通过验证,与浏览器返回的结果一致,这表明加密过程已成功复现。全球量化策略源码
通过全局搜索h5st字符串,定位到其位置,结合单步调试,最终逆向得到h5st 4.2源码的部分代码片段。在nodejs环境中调试请求,成功获取数据,标志着逆向研究的完成。
综上所述,本文详细阐述了京东m端h5st 4.2签名算法的逆向过程,包括参数解析、加密解码、代码分析以及实际请求实现,为理解京东API接口的加密机制提供了直观的路径。
Debug版本和Release版本有什么区别?
一、Debug 和 Release 编译方式的本质区别
Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
Debug 和 Release 的真正秘密,在于一组编译选项。下面列出了分别针对二者的选项(当然除此之外还有其他一些,如/Fd /Fo,但区别并不重要,通常他们也不会引起 Release 版错误,在此不讨论)
Debug 版本:
/MDd /MLd 或 /MTd 使用 Debug runtime library(调试版本的运行时刻函数库)
/Od 关闭优化开关
/D "_DEBUG" 相当于 #define _DEBUG,打开编译调试代码开关(主要针对
assert函数)
/ZI 创建 Edit and continue(编辑继续)数据库,这样在调试过
程中如果修改了源代码不需重新编译
/GZ 可以帮助捕获内存错误
/Gm 打开最小化重链接开关,减少链接时间
Release 版本:
/MD /ML 或 /MT 使用发布版本的运行时刻函数库
/O1 或 /O2 优化开关,使程序最小或最快
/D "NDEBUG" 关闭条件编译调试代码开关(即不编译assert函数)
/GF 合并重复的字符串,并将字符串常量放到只读内存,防止
被修改
实际上,Debug 和 Release 并没有本质的界限,他们只是一组编译选项的集合,编译器只是按照预定的选项行动。事实上,我们甚至可以修改这些选项,从而得到优化过的调试版本或是带跟踪语句的发布版本。
二、哪些情况下 Release 版会出错
有了上面的介绍,我们再来逐个对照这些选项看看 Release 版错误是怎样产生的
1. Runtime Library:链接哪种运行时刻函数库通常只对程序的性能产生影响。调试版本的 Runtime Library 包含了调试信息,并采用了一些保护机制以帮助发现错误,因此性能不如发布版本。编译器提供的 Runtime Library 通常很稳定,不会造成 Release 版错误;倒是由于 Debug 的 Runtime Library 加强了对错误的检测,如堆内存分配,有时会出现 Debug 有错但 Release 正常的现象。应当指出的是,如果 Debug 有错,即使 Release 正常,程序肯定是有 Bug 的,只不过可能是 Release 版的某次运行没有表现出来而已。
2. 优化:这是造成错误的主要原因,因为关闭优化时源程序基本上是转转最新源码直接翻译的,而打开优化后编译器会作出一系列假设。这类错误主要有以下几种:
(1) 帧指针(Frame Pointer)省略(简称 FPO ):在函数调用过程中,所有调用信息(返回地址、参数)以及自动变量都是放在栈中的。若函数的声明与实现不同(参数、返回值、调用方式),就会产生错误————但 Debug 方式下,栈的访问通过 EBP 寄存器保存的地址实现,如果没有发生数组越界之类的错误(或是越界“不多”),函数通常能正常执行;Release 方式下,优化会省略 EBP 栈基址指针,这样通过一个全局指针访问栈就会造成返回地址错误是程序崩溃。C++ 的强类型特性能检查出大多数这样的错误,但如果用了强制类型转换,就不行了。你可以在 Release 版本中强制加入 /Oy- 编译选项来关掉帧指针省略,以确定是否此类错误。此类错误通常有:
● MFC 消息响应函数书写错误。正确的应为
afx_msg LRESULT OnMessageOwn(WPARAM wparam, LPARAM lparam);
ON_MESSAGE 宏包含强制类型转换。防止这种错误的方法之一是重定义 ON_MESSAGE 宏,把下列代码加到 stdafx.h 中(在#include "afxwin.h"之后),函数原形错误时编译会报错
#undef ON_MESSAGE
#define ON_MESSAGE(message, memberFxn) { message, 0, 0, 0, AfxSig_lwl, (AFX_PMSG)(AFX_PMSGW)(static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) > (&memberFxn) },
(2) volatile 型变量:volatile 告诉编译器该变量可能被程序之外的未知方式修改(如系统、其他进程和线程)。优化程序为了使程序性能提高,常把一些变量放在寄存器中(类似于 register 关键字),而其他进程只能对该变量所在的内存进行修改,而寄存器中的值没变。如果你的程序是多线程的,或者你发现某个变量的值与预期的不符而你确信已正确的设置了,则很可能遇到这样的问题。这种错误有时会表现为程序在最快优化出错而最小优化正常。把你认为可疑的变量加上 volatile 试试。
(3) 变量优化:优化程序会根据变量的使用情况优化变量。例如,函数中有一个未被使用的变量,在 Debug 版中它有可能掩盖一个数组越界,而在 Release 版中,这个变量很可能被优化调,此时数组越界会破坏栈中有用的数据。当然,实际的情况会比这复杂得多。与此有关的错误有:
● 非法访问,包括数组越界、指针错误等。例如
void fn(void)
{
int i;
i = 1;
int a[4];
{
int j;
j = 1;
}
a[-1] = 1;//当然错误不会这么明显,例如下标是变量
a[4] = 1;
}
j 虽然在数组越界时已出了作用域,但其空间并未收回,因而 i 和 j 就会掩盖越界。而 Release 版由于 i、j 并未其很大作用可能会被优化掉,从而使栈被破坏。
3. _DEBUG 与 NDEBUG :当定义了 _DEBUG 时,assert() 函数会被编译,而 NDEBUG 时不被编译。除此之外,VC++中还有一系列断言宏。这包括:
ANSI C 断言 void assert(int expression );
C Runtime Lib 断言 _ASSERT( booleanExpression );
_ASSERTE( booleanExpression );
MFC 断言 ASSERT( booleanExpression );
VERIFY( booleanExpression );
ASSERT_VALID( pObject );
ASSERT_KINDOF( classname,战队介绍网站源码 pobject );
ATL 断言 ATLASSERT( booleanExpression );
此外,TRACE() 宏的编译也受 _DEBUG 控制。
所有这些断言都只在 Debug版中才被编译,而在 Release 版中被忽略。唯一的例外是 VERIFY() 。事实上,这些宏都是调用了 assert() 函数,只不过附加了一些与库有关的调试代码。如果你在这些宏中加入了任何程序代码,而不只是布尔表达式(例如赋值、能改变变量值的函数调用 等),那么 Release 版都不会执行这些操作,从而造成错误。初学者很容易犯这类错误,查找的方法也很简单,因为这些宏都已在上面列出,只要利用 VC++ 的 Find in Files 功能在工程所有文件中找到用这些宏的地方再一一检查即可。另外,有些高手可能还会加入 #ifdef _DEBUG 之类的条件编译,也要注意一下。
顺便值得一提的是 VERIFY() 宏,这个宏允许你将程序代码放在布尔表达式里。这个宏通常用来检查 Windows API 的返回值。有些人可能为这个原因而滥用 VERIFY() ,事实上这是危险的,因为 VERIFY() 违反了断言的思想,不能使程序代码和调试代码完全分离,最终可能会带来很多麻烦。因此,专家们建议尽量少用这个宏。
4. /GZ 选项:这个选项会做以下这些事
(1) 初始化内存和变量。包括用 0xCC 初始化所有自动变量,0xCD ( Cleared Data ) 初始化堆中分配的内存(即动态分配的内存,例如 new ),0xDD ( Dead Data ) 填充已被释放的堆内存(例如 delete ),0xFD( deFencde Data ) 初始化受保护的内存(debug 版在动态分配内存的前后加入保护内存以防止越界访问),其中括号中的词是微软建议的助记词。这样做的好处是这些值都很大,作为指针是不可能的(而且 位系统中指针很少是奇数值,在有些系统中奇数的指针会产生运行时错误),作为数值也很少遇到,而且这些值也很容易辨认,因此这很有利于在 Debug 版中发现 Release 版才会遇到的错误。要特别注意的是,很多人认为编译器会用 0 来初始化变量,这是错误的(而且这样很不利于查找错误)。
(2) 通过函数指针调用函数时,会通过检查栈指针验证函数调用的匹配性。(防止原形不匹配)
(3) 函数返回前检查栈指针,确认未被修改。(防止越界访问和原形不匹配,与第二项合在一起可大致模拟帧指针省略 FPO )
通常 /GZ 选项会造成 Debug 版出错而 Release 版正常的现象,因为 Release 版中未初始化的变量是随机的,这有可能使指针指向一个有效地址而掩盖了非法访问。
除此之外,/Gm /GF 等选项造成错误的情况比较少,而且他们的效果显而易见,比较容易发现。jquery数量加减源码
三、怎样“调试” Release 版的程序
遇到 Debug 成功但 Release 失败,显然是一件很沮丧的事,而且往往无从下手。如果你看了以上的分析,结合错误的具体表现,很快找出了错误,固然很好。但如果一时找不出,以下给出了一些在这种情况下的策略。
1. 前面已经提过,Debug 和 Release 只是一组编译选项的差别,实际上并没有什么定义能区分二者。我们可以修改 Release 版的编译选项来缩小错误范围。如上所述,可以把 Release 的选项逐个改为与之相对的 Debug 选项,如 /MD 改为 /MDd、/O1 改为 /Od,或运行时间优化改为程序大小优化。注意,一次只改一个选项,看改哪个选项时错误消失,再对应该选项相关的错误,针对性地查找。这些选项在 Project\Settings... 中都可以直接通过列表选取,通常不要手动修改。由于以上的分析已相当全面,这个方法是最有效的。
2. 在编程过程中就要时常注意测试 Release 版本,以免最后代码太多,时间又很紧。
3. 在 Debug 版中使用 /W4 警告级别,这样可以从编译器获得最大限度的错误信息,比如 if( i =0 )就会引起 /W4 警告。不要忽略这些警告,通常这是你程序中的 Bug 引起的。但有时 /W4 会带来很多冗余信息,如 未使用的函数参数 警告,而很多消息处理函数都会忽略某些参数。我们可以用
#progma warning(disable: ) //禁止
//...
#progma warning(default: ) //重新允许
来暂时禁止某个警告,或使用
#progma warning(push, 3) //设置警告级别为 /W3
//...
#progma warning(pop) //重设为 /W4
来暂时改变警告级别,有时你可以只在认为可疑的那一部分代码使用 /W4。
4.你也可以像 Debug 一样调试你的 Release 版,只要加入调试符号。在 Project/Settings... 中,选中 Settings for "Win Release",选中 C/C++ 标签,Category 选 General,Debug Info 选 Program Database。再在 Link 标签 Project options 最后加上 "/OPT:REF" (引号不要输)。这样调试器就能使用 pdb 文件中的调试符号。但调试时你会发现断点很难设置,变量也很难找到——这些都被优化过了。不过令人庆幸的是,Call Stack 窗口仍然工作正常,即使帧指针被优化,栈信息(特别是返回地址)仍然能找到。这对定位错误很有帮助。
如何在C语言中嵌入汇编?
在 Visual C++ 中使用内联汇编- -
使用内联汇编可以在 C/C++ 代码中嵌入汇编语言指令,而且不需要额外的汇编和连接步骤。在 Visual C++ 中,内联汇编是内置的编译器,因此不需要配置诸如 MASM 一类的独立汇编工具。这里,我们就以 Visual Studio .NET 为背景,介绍在 Visual C++ 中使用内联汇的相关知识(如果是早期的版本,可能会有些许出入)。
内联汇编代码可以使用 C/C++ 变量和函数,因此它能非常容易地整合到 C/C++ 代码中。它能做一些对于单独使用 C/C++ 来说非常笨重或不可能完成的任务。
一、 优点
使用内联汇编可以在 C/C++ 代码中嵌入汇编语言指令,而且不需要额外的汇编和连接步骤。在 Visual C++ 中,内联汇编是内置的编译器,因此不需要配置诸如 MASM 一类的独立汇编工具。这里,我们就以 Visual Studio .NET 为背景,介绍在 Visual C++ 中使用内联汇的相关知识(如果是早期的版本,可能会有些许出入)。
内联汇编代码可以使用 C/C++ 变量和函数,因此它能非常容易地整合到 C/C++ 代码中。它能做一些对于单独使用 C/C++ 来说非常笨重或不可能完成的任务。
内联汇编的用途包括:
使用汇编语言编写特定的函数;
编写对速度要求非常较高的代码;
在设备驱动程序中直接访问硬件;
编写 naked 函数的初始化和结束代码。
二、 关键字
使用内联汇编要用到 __asm 关键字,它可以出现在任何允许 C/C++ 语句出现的地方。我们来看一些例子:
简单的 __asm 块:
__asm
{
MOV AL, 2
MOV DX, 0xD
OUT AL, DX
}
在每条汇编指令之前加 __asm 关键字:
__asm MOV AL, 2
__asm MOV DX, 0xD
__asm OUT AL, DX
因为 __asm 关键字是语句分隔符,所以可以把多条汇编指令放在同一行:
__asm MOV AL, 2 __asm MOV DX, 0xD __asm OUT AL, DX
显然,第一种方法与 C/C++ 的风格很一致,并且把汇编代码和 C/C++ 代码清楚地分开,还避免了重复输入 __asm 关键字,因此推荐使用第一种方法。
不像在 C/C++ 中的"{ }",__asm 块的"{ }"不会影响 C/C++ 变量的作用范围。同时,__asm 块可以嵌套,而且嵌套也不会影响变量的作用范围。
为了与低版本的 Visual C++ 兼容,_asm 和 __asm 具有相同的意义。另外,Visual C++ 支持标准 C++ 的 asm 关键字,但是它不会生成任何指令,它的作用仅限于使编译器不会出现编译错误。要使用内联汇编,必须使用 __asm 而不是 asm 关键字。
三、 汇编语言
1. 指令集
内联汇编支持 Intel Pentium 4 和 AMD Athlon 的所有指令。更多其它处理器的指令可以通过 _EMIT 伪指令来创建(_EMIT 伪指令说明见下文)。
2. MASM 表达式
在内联汇编代码中,可以使用所有的 MASM 表达式(MASM 表达式是指用来计算一个数值或一个地址的操作符和操作数的组合)。
3. 数据指示符和操作符
虽然 __asm 块中允许使用 C/C++ 的数据类型和对象,但它不能使用 MASM 指示符和操作符来定义数据对象。这里特别指出,__asm 块中不允许 MASM 中的定义指示符(DB、DW、DD、DQ、DT 和 DF),也不允许使用 DUP 和 THIS 操作符。MASM 中的结构和记录也不再有效,内联汇编不接受 STRUC、RECORD、WIDTH 或者 MASK。
4. EVEN 和 ALIGN 指示符
尽管内联汇编不支持大多数 MASM 指示符,但它支持 EVEN 和 ALIGN。当需要的时候,这些指示符在汇编代码里面加入 NOP 指令(空操作)使标号对齐到特定边界。这样可以使某些处理器取指令时具有更高的效率。
5. MASM 宏指示符
内联汇编不是宏汇编,不能使用 MASM 宏指示符(MACRO、REPT、IRC、IRP 和 ENDM)和宏操作符(<>、!、&、% 和 .TYPE)。
6. 段
必须使用寄存器而不是名称来指明段(段名称"_TEXT"是无效的)。并且,段跨越必须显式地说明,如 ES:[EBX]。
7. 类型和变量大小
在内联汇编中,可以用 LENGTH、SIZE 和 TYPE 来获取 C/C++ 变量和类型的大大小。
* LENGTH 操作符用来取得 C/C++ 中数组的元素个数(如果不是一个数组,则结果为 1)。
* SIZE 操作符可以获取 C/C++ 变量的大小(一个变量的大小是 LENGTH 和 TYPE 的乘积)。
* TYPE 操作符可以返回 C/C++ 类型和变量的大小(如果变量是一个数组,它得到的是数组中单个元素的大小)。
例如,程序中定义了一个 8 维的整数型变量:
int iArray[8];
下面是 C 和汇编表达式中得到的 iArray 及其元素的相关值:
__asm C Size
LENGTH iArray sizeof(iArray)/sizeof(iArray[0]) 8
SIZE iArray sizeof(iArray)
TYPE iArray sizeof(iArray[0]) 4
8. 注释
内联汇编中可以使用汇编语言的注释,即";"。例如:
__asm MOV EAX, OFFSET pbBuff ; Load address of pbBuff
因为 C/C++ 宏将会展开到一个逻辑行中,为了避免在宏中使用汇编语言注释带来的混乱,内联汇编也允许使用 C/C++ 风格的注释。
9. _EMIT 伪指令
_EMIT 伪指令相当于 MASM 中的 DB,但是 _EMIT 一次只能在当前代码段(.text 段)中定义一个字节。例如:
__asm
{
JMP _CodeLabel
_EMIT 0x ; 定义混合在代码段的数据
_EMIT 0x
_CodeLabel: ; 这里是代码
_EMIT 0x ; NOP指令
}
. 寄存器使用
一般来说,不能假定某个寄存器在 __asm 块开始的时候有已知的值。寄存器的值将不能保证会从 __asm 块保留到另外一个 __asm 块中。
如果一个函数声明为 __fastcall 调用方式,则其参数将通过寄存器而不是堆栈来传递。这将会使 __asm 块产生问题,因为函数无法被告知哪个参数在哪个寄存器中。如果函数接收了 EAX 中的参数并立即储存一个值到 EAX 中的话,原来的参数将丢失掉。另外,在所有声明为 __fastcall 的函数中,ECX 寄存器是必须一直保留的。为了避免以上的冲突,包含 __asm 块的函数不要声明为 __fastcall 调用方式。
提示:如果使用 EAX、EBX、ECX、EDX、ESI 和 EDI 寄存器,你不需要保存它。但如果你用到了 DS、SS、SP、BP 和标志寄存器,那就应该用 PUSH 保存这些寄存器。
提示:如果程序中改变了用于 STD 和 CLD 的方向标志,必须将其恢复到原来的值。
四、 使用 C/C++ 元素
1. 可用的 C/C++ 元素
C/C++ 与汇编语言可以混合使用,在内联汇编中可以使用 C/C++ 变量以及很多其它的 C/C++ 元素,包括:
符号,包括标号、变量和函数名;
常量,包括符号常量和枚举型成员;
宏定义和预处理指示符;
注释,包括"/**/"和"//";
类型名,包括所有 MASM 中合法的类型;
typedef 名称,通常使用 PTR 和 TYPE 操作符,或者使用指定的的结构或枚举成员。
在内联汇编中,可以使用 C/C++ 或汇编语言的基数计数法。例如,0x 和 H 是相等的。
2. 操作符使用
内联汇编中不能使用诸如"<<"一类的 C/C++ 操作符。但是,C/C++ 和 MASM 共有的操作符(比如"*"和"[]"操作符),都被认为是汇编语言的操作符,是可以使用的。举个例子:
int iArray[];
__asm MOV iArray[6], BX ; Store BX at iArray + 6 (Not scaled)
iArray[6] = 0; // Store 0 at iArray+ (Scaled)
提示:在内联汇编中,可以使用 TYPE 操作符使其与 C/C++ 一致。比如,下面两条语句是一样的:
__asm MOV iArray[6 * TYPE int], 0 ; Store 0 at iArray +
iArray[6] = 0; // Store 0 at iArray +
3. C/C++ 符号使用
在 __asm 块中可以引用所有在作用范围内的 C/C++ 符号,包括变量名称、函数名称和标号。但是不能访问 C++ 类的成员函数。
下面是在内联汇编中使用 C/C++ 符号的一些限制:
每条汇编语句只能包含一个 C/C++ 符号。在一条汇编指令中,多个符号只能出现在 LENGTH、TYPE 或 SIZE 表达式中。
在 __asm 块中引用函数必须先声明。否则,编译器将不能区别 __asm 块中的函数名和标号。
在 __asm 块中不能使用对于 MASM 来说是保留字的 C/C++ 符号(不区分大小写)。MASM 保留字包含指令名称(如 PUSH)和寄存器名称(如 ESI)等。
在 __asm 块中不能识别结构和联合标签。
4. 访问 C/C++ 中的数据
内联汇编的一个非常大的方便之处是它可以使用名称来引用 C/C++ 变量。例如,如果 C/C++ 变量 iVar 在作用范围内:
__asm MOV EAX, iVar ; Stores the value of iVar in EAX
如果 C/C++ 中的类、结构或者枚举成员具有唯一的名称,则在 __asm 块中可以只通过成员名称来访问(省略"."操作符之前的变量名或 typedef 名称)。然而,如果成员不是唯一的,你必须在"."操作符之前加上变量名或 typedef 名称。例如,下面的两个结构都具有 SameName 这个成员变量:
struct FIRST_TYPE
{
char *pszWeasel;
int SameName;
};
struct SECOND_TYPE
{
int iWonton;
long SameName;
};
如果按下面方式声明变量:
struct FIRST_TYPE ftTest;
struct SECOND_TYPE stTemp;
那么,所有引用 SameName 成员的地方都必须使用变量名,因为 SameName 不是唯一的。另外,由于上面的 pszWeasel 变量具有唯一的名称,你可以仅仅使用它的成员名称来引用它:
__asm
{
MOV EBX, OFFSET ftTest
MOV ECX, [EBX]ftTest.SameName ; 必须使用"ftTest"
MOV ESI, [EBX]. pszWeasel ; 可以省略"ftTest"
}
提示:省略变量名仅仅是为了书写代码方便,生成的汇编指令还是一样的。
5. 用内联汇编写函数
如果用内联汇编写函数的话,要传递参数和返回一个值都是非常容易的。看下面的例子,比较一下用独立汇编和内联汇编写的函数:
; PowerAsm.asm
; Compute the power of an integer
PUBLIC GetPowerAsm
_TEXT SEGMENT WORD PUBLIC 'CODE'
GetPowerAsm PROC
PUSH EBP ; Save EBP
MOV EBP, ESP ; Move ESP into EBP so we can refer
; to arguments on the stack
MOV EAX, [EBP+4] ; Get first argument
MOV ECX, [EBP+6] ; Get second argument
SHL EAX, CL ; EAX = EAX * (2 ^ CL)
POP EBP ; Restore EBP
RET ; Return with sum in EAX
GetPowerAsm ENDP
_TEXT ENDS
END
C/C++ 函数一般用堆栈来传递参数,所以上面的函数中需要通过堆栈位置来访问它的参数(在 MASM 或其它一些汇编工具中,也允许通过名称来访问堆栈参数和局部堆栈变量)。
下面的程序是使用内联汇编写的:
// PowerC.c
#include
int GetPowerC(int iNum, int iPower);
int main()
{
printf("3 times 2 to the power of 5 is %d\n", GetPowerC( 3, 5));
}
int GetPowerC(int iNum, int iPower)
{
__asm
{
MOV EAX, iNum ; Get first argument
MOV ECX, iPower ; Get second argument
SHL EAX, CL ; EAX = EAX * (2 to the power of CL)
}
// Return with result in EAX
}
使用内联汇编写的 GetPowerC 函数可以通过参数名称来引用它的参数。由于 GetPowerC 函数没有执行 C 的 return 语句,所以编译器会给出一个警告信息,我们可以通过 #pragma warning 禁止生成这个警告。
内联汇编的其中一个用途是编写 naked 函数的初始化和结束代码。对于一般的函数,编译器会自动帮我们生成函数的初始化(构建参数指针和分配局部变量等)和结束代码(平衡堆栈和返回一个值等)。使用内联汇编,我们可以自己编写干干净净的函数。当然,此时我们必须自己动手做一些有关函数初始化和扫尾的工作。例如:
void __declspec(naked) MyNakedFunction()
{
// Naked functions must provide their own prolog.
__asm
{
PUSH EBP
MOV ESP, EBP
SUB ESP, __LOCAL_SIZE
}
.
.
.
// And we must provide epilog.
__asm
{
POP EBP
RET
}
}
6. 调用 C/C++ 函数
内联汇编中调用声明为 __cdecl 方式(默认)的 C/C++ 函数必须由调用者清除参数堆栈,下面是一个调用 C/C++ 函数例子:
#include
char szFormat[] = "%s %s\n";
char szHello[] = "Hello";
char szWorld[] = " world";
void main()
{
__asm
{
MOV EAX, OFFSET szWorld
PUSH EAX
MOV EAX, OFFSET szHello
PUSH EAX
MOV EAX, OFFSET szFormat
PUSH EAX
CALL printf
// 压入了 3 个参数在堆栈中,调用函数之后要调整堆栈
ADD ESP,
}
}
提示:参数是按从右往左的顺序压入堆栈的。
如果调用 __stdcall 方式的函数,则不需要自己清除堆栈。因为这种函数的返回指令是 RET n,会自动清除堆栈。大多数 Windows API 函数均为 __stdcall 调用方式(仅除 wsprintf 等几个之外),下面是一个调用 MessageBox 函数的例子:
#include
TCHAR g_tszAppName[] = TEXT("API Test");
void main()
{
TCHAR tszHello[] = TEXT("Hello, world!");
__asm
{
PUSH MB_OK OR MB_ICONINFORMATION
PUSH OFFSET g_tszAppName ; 全局变量用 OFFSET
LEA EAX, tszHello ; 局部变量用 LEA
PUSH EAX
PUSH 0
CALL DWORD PTR [MessageBox] ; 注意这里不是 CALL MessageBox,而是调用重定位过的函数地址
}
}
提示:可以不受限制地访问 C++ 成员变量,但是不能访问 C++ 的成员函数。
7. 定义 __asm 块为 C/C++ 宏
使用 C/C++ 宏可以方便地把汇编代码插入到源代码中。但是这其中需要额外地注意,因为宏将会扩展到一个逻辑行中。
为了不会出现问题,请按以下规则编写宏:
使用花括号把 __asm 块包围住;
把 __asm 关键字放在每条汇编指令之前;
使用经典 C 风格的注释("/* comment */"),不要使用汇编风格的注释("; comment")或单行的 C/C++ 注释("// comment");
举个例子,下面定义了一个简单的宏:
#define PORTIO __asm \
/* Port output */ \
{ \
__asm MOV AL, 2 \
__asm MOV DX, 0xD \
__asm OUT DX, AL \
}
乍一看来,后面的三个 __asm 关键字好像是多余的。其实它们是需要的,因为宏将被扩展到一个单行中:
__asm /* Port output */ { __asm MOV AL, 2 __asm MOV DX, 0xD __asm OUT DX, AL }
从扩展后的代码中可以看出,第三个和第四个 __asm 关键字是必须的(作为语句分隔符)。在 __asm 块中,只有 __asm 关键字和换行符会被认为是语句分隔符,又因为定义为宏的一个语句块会被认为是一个逻辑行,所以必须在每条指令之前使用 __asm 关键字。
括号也是需要的,如果省略了它,编译器将不知道汇编代码在哪里结束,__asm 块后面的 C/C++ 语句看起来会被认为是汇编指令。
同样是由于宏展开的原因,汇编风格的注释("; comment")和单行的 C/C++ 注释("// commen")也可能会出现错误。为了避免这些错误,在定义 __asm 块为宏时请使用经典 C 风格的注释("/* comment */")。
和 C/C++ 宏一样 __asm 块写的宏也可以拥有参数。和 C/C++ 宏不一样的是,__asm 宏不能返回一个值,因此,不能使用这种宏作为 C/C++ 表达式。
不要不加选择地调用这种类型的宏。比如,在声明为 __fastcall 的函数中调用汇编语言宏可能会导致不可预料的结果(请参看前文的说明)。
8. 转跳
可以在 C/C++ 里面使用 goto 转跳到 __asm 块中的标号处,也可以在 __asm 块中转跳到 __asm 块里面或外面的标号处。__asm 块内的标号是不区分大小写的(指令、指示符等也是不区分大小写的)。例如:
void MyFunction()
{
goto C_Dest; /* 正确 */
goto c_dest; /* 错误 */
goto A_Dest; /* 正确 */
goto a_dest; /* 正确 */
__asm
{
JMP C_Dest ; 正确
JMP c_dest ; 错误
JMP A_Dest ; 正确
JMP a_dest ; 正确
a_dest: ; __asm 标号
}
C_Dest: /* C/C++ 标号 */
return;
}
不要使用函数名称当作标号,否则将转跳到函数中执行,而不是标号处。例如,由于 exit 是 C/C++ 的函数,下面的转跳将不会到 exit 标号处:
; 错误:使用函数名作为标号
JNE exit
.
.
.
exit:
.
.
.
美元符号"$"用于指定当前指令位置,常用于条件跳转中,例如:
JNE $+5 ; 下面这条指令的长度是 5 个字节
JMP _Label
NOP ; $+5,转跳到了这里
.
.
.
_Label:
.
.
.
五、在 Visual C++ 工程中使用独立汇编
内联汇编代码不易于移植,如果你的程序打算在不同类型的机器(比如 x 和 Alpha)上运行,你可能需要在不同的模块中使用特定的机器代码。这时候你可以使用 MASM(Microsoft Macro Assembler),因为 MASM 支持更多方便的宏指令和数据指示符。
这里简单介绍一下在 Visual Studio .NET 中调用 MASM 编译独立汇编文件的步骤。
在 Visual C++ 工程中,添加按 MASM 的要求编写的 .asm 文件。在解决方案资源管理器中,右击这个文件,选择"属性"菜单项,在属性对话框中,点击"自定义生成步骤",设置如下项目:
命令行:ML.exe /nologo /c /coff "-Fo$(IntDir)\$(InputName).obj" "$(InputPath)"
输出:$(IntDir)\$(InputName).obj
如果要生成调试信息,可以在命令行中加入"/Zi"参数,还可以根据需要生成 .lst 和 .sbr 文件。
如果要在汇编文件中调用 Windows API,可以从网上下载 MASM 包(包含了 MASM 汇编工具、非常完整的 Windows API 头文件/库文件、实用宏以及大量的 Win 汇编例子等)。相应地,应该在命令行中加入"/I X:\MASM\INCLUDE"参数指定 Windows API 汇编头文件(.inc)的路径。MASM 的主页是:,里面可以下载最新版本的 MASM 包。