c语言中神奇的通信码,对《神奇的C语言》文中例子 5 代码的分析讨论

在春节前,我曾经参与在《神奇的C语言》一文中的例子(5)的讨论,但限于评论内容的有限,如今本文再次对这个问题单独讨论。(此问题原貌,详见《神奇的C语言》,这里我将原文中的代码稍作轻微改动,并从新给出以下)html

原问题给出以下代码:数组

#include

void func1(chara[])

{

//这里的参数 a 为指向数组的指针,所以 &a 和 a 的意义不一样(前者为指针变量的地址,后者为指针变量的值)

//&a 表示指针变量的地址。

//&a[0] 等效为 a ,即指针变量的值。

_tprintf(_T("func1: &a = 0x%08X; &a[0] = 0x%08X;\n"), &a, &a[0]);

}int _tmain(int argc, _TCHAR*argv[])

{char a[10];

//这里的 a 是数组名,至关于字面地址,因此 &a 至关于直接写成 a 。

_tprintf(_T("wmain: &a = 0x%08X; &a[0] = 0x%08X;\n"), &a, &a[0]);

//数组名做为参数传递给其余函数时,退化为指针

func1(a);return 0;

}

以 VS2005 编译,采用默认项目配置(Unicode 编码),在 Release 版本的输出结果以下(可见 func1 中的 &a 和其余输出不一样,且相差 4 ,在 debug 版本下此差值是一个较大的数值):cookie

----------------------------------------------------函数

Output:post

----------------------------------------------------优化

wmain: &a = 0x0018FF38; &a[0] = 0x0018FF38;编码

func1 : &a = 0x0018FF34; &a[0] = 0x0018FF38;url

----------------------------------------------------spa

以 IDA 反汇编 Release 版本的可执行文件,获得 wmain 函数的汇编代码以下:debug

wmain proc near

var_14 = dword ptr -14h ;func1 的实际参数(char* a)

var_10 = dword ptr -10h ;a 的起始地址

var_4 = dword ptr -4 ;用于 ESP 校验

sub esp, 14h ;为临时变量分配空间

moveax, __security_cookiexoreax, espmov[esp+14h+var_4], eax ;保存 ( ESP ^ _security_cookie ) 到 var_4pushesimov esi, ds:__imp__wprintfleaeax, [esp+18h+var_10] ;wmain: &a[0] (0018FF38)pusheaxmovecx, eax ;wmain: &a (0018FF38)pushecxpush offset pStr1 ;字符串 "wmain: &a = 0x%08X; &a[0] = 0x%08X;\n"

call esi ;__imp__wprintf 打印输出

leaedx, [esp+24h+var_10] ;func1: &a[0] (0018FF38)moveax, edxpusheaxleaecx, [esp+28h+var_14] ;func1: &a (0018FF34), 参数的地址pushecxpush offset pStr2 ;字符串 "func1: &a = 0x%08X; &a[0] = 0x%08X;\n"

mov[esp+30h+var_14], edx ;为实际参数赋值call esi ;__imp__wprintf 打印输出

movecx, [esp+30h+var_4]addesp, 18h ;为以上两次 _tprintf 函数调用复原栈指针popesixorecx, espxoreax, eaxcall__security_check_cookie ;检查 ESP 是否被意外破坏addesp, 14h ;释放栈上的临时变量空间retn

从以上汇编代码,能够获得关于 Release 版本代码(以优化运行效率为主要目标)的以下结论:

(1)直接使用 ESP 寻址函数内的临时变量或参数。

(2)func1 函数调用被编译器直接内联到 wmain 函数体内。在内联 func1 时,编译器对代码作了等效性变换,代码和栈上数据的顺序,与一般函数调用相比有细微差异,但运行结果是等效的。

(3)在寄存器保护环节,保存了 ESI (目标索引)寄存器,其用意是以 ESI 加载 __imp__wprintf 的运行时(绑定后)地址。(对于默认配置,此函数是来自 VS2005 运行时库 msvcr80.dll 中的导入函数,函数地址位于导入表中,在加载时被绑定)

下面是根据以上汇编代码获得的 wmain 函数的栈上数据示意图(图中栈的增加方向为从下向上,并已经根据 输出结果 推算出了栈上的虚拟地址):

4b6b8a199d4bf21572b3ce72e839eb52.png

上面的表格中包括了两次对 __imp__wprintf 调用时的参数,其中 __imp__wprintf 的栈帧,除了参数以外的其他部分在表中没有显示,便可以认为上表是第二次 __imp_wprintf 已返回到 wmain 函数时的栈上数据快照,两次函数调用的复原栈指针(即释放参数占用的空间)被合并为一条指令(add esp, 18h)。表格中的红色数据,即为代码中交由 _tprintf 打印输出的值。其中 pStr1 和 pStr2 指向位于 .rdata section(只读数据段)上的字符串(根据项目选项,为 Unicode 编码)。

其中 ESP 校验过程为,在 wmain 函数的起始位置,为临时变量分配空间后,将此时的 ESP 和一个特定常数(_security_cookie)异或,结果保存到 wmain 的第一个临时变量(var1)中,以后调用了 __imp__wprintf 等其余函数后,把 ESP 和 var1 异或的结果保存到 ECX 中(此时 ECX 的期待值为 _security_cookie),而后检测 ECX 和 _security_cookie 是否相等便可。

【注】:表格中的栈指针校验值,根据汇编代码能够看出,至关于首个出现的函数临时变量,它的值的意义是,为临时变量分配空间后 (T1 时刻),将此时的 ESP 和一个常量值异或,存储于该临时变量。在复原栈指针以前(T2 时刻),校验 ESP 是否吻合 T1 时刻的值。 -- hoodlum1980,2014-4-11

综合以上图表,对代码输出则能够比较容易作出解释:

第一行输出结果为 wmain 函数中的 &a 和 a (a 为数组名)在写法上等效的体现,在 wmain 里 a 为本地数组的数组名(这里”本地“的含义指的是对其声明的可见性),若是把 a 理解为数组,&a 表示数组的存储地址,若是把 a 理解为至关于数组元素指针,则 &a 不具备实际物理意义,所以 &a 和 &a[0] 都等效于 a,即数组的起始地址。

第二行输出结果为 func1 函数中的 &a 和 a (a 为指针变量)在乎义上不一样的体现,a 是一个指向数组的指针变量(以及 func1 的实际参数),&a 表示此指针变量的地址,&a[0] 表示被指向数组的起始地址,即 &a[0] =  a + 0 * sizeof (char) = a (这里为数学计算含义),  即指针变量 a 的值。在本例输出中,func1 的实际参数 a 与”数组起始地址“紧邻,a 的地址为 0018FF34h,a 的值为 0018FF38h(指向 wmain 中的数组)。

所以,本范例的代码,能够认为在原理上即至关于以下代码:

int _tmain(int argc, _TCHAR*argv[])

{//main 中的结果:

char a[10];

_tprintf(_T("main_: &a = 0x%08X; &a[0] = 0x%08X;\n"), a, a);//func1 中的结果

char *p =a;

_tprintf(_T("func1: &a = 0x%08X; &a[0] = 0x%08X;\n"), &p, p);return 0;

}

【附】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值