delphi 2006中,使用stdcall调用约定时,压缩结构参数的bug分析

问题

今天遇到一个很奇怪的问题,有一个dephi2006写的dll,使用了stdcall的调用约定,参数传递了结构体,在函数中收到的结构体值和传入的不一致,最后一个boolean类型,应为False,收到的是True,如下图:

代码

//结构体定义
RStruct = packed record
    i1: Integer;
    i2: Integer;
    i3: Integer;
    i4: Integer;
    i5: Integer;
    b1: Boolean;
    b2: Boolean;
    b3: Boolean;
end;


procedure Fun(Param: RStruct);stdcall;
begin
  Writeln('函数收到参数:');

  Param.Print();
end;

var
  Param: RStruct;
begin
  Param.i1 := 0;
  Param.i2 := 1;
  Param.i3 := 2;
  Param.i4 := 3;
  Param.i5 := 4;
  Param.b1 := true;
  Param.b2 := False;
  Param.b3 := false;

  //Writeln('调用参数:');

  //Param.Print();

  //Writeln(LR + LR);

  Fun(Param);

  Readln;
end.

分析

为简化生成的汇编代码,删除掉了输出日志代码,只保留了必要的部分:

这里先看一个正确的例子,删除掉结构体中的i5,让结构体变小一点(在这种情况下,结果是正确的)

汇编代码如下:

关键的部分为以下内容,是对结构体中后3个布尔类型的传参

//eax 是一个32位的寄存器
//[edx + $12] 位置存储的是 b3 的值,为true(即数值1)
//此处把b3值写入到 eax 
movzx eax,[edx + $12] //eax值变为 $00000001

//eax左移16位 
shl eax, $10 //eax的值变为 $00010000

//ax寄存器为eax寄存器的低16位
//[edx + $10] 为b1,由于b1 占一个字节,b2占一个字节
//此处把b1 b2 移动到ax中(eax两个低字节)
mov ax, [edx + $10]

//eax 的布局为 [空] [b3] [b2] [b1]

//把eax 压入堆栈(压入堆栈是为了给函数传参用)
push eax


再看一下错误的例子

汇编代码如下图:


movzx eax,[edx + $14]

shl eax, $10

mov ax, [edx + $14]

push eax

//除了结构体大小变化引起的偏移量不同外,剩下的就是 
//movzx eax,[edx + $14] 和 mov ax, [edx + $14],后面的地址是相同的,都指向了b1,而前面正确的示例中 movzx指向的是b3

//这就导致最终eax中的布局变成了 [空] [b1] [b2] [b1]

总结

只有在Fun声明为stdcall时会出现这种错误,改为默认的调用约定可以正常工作, 结构体去掉packed声明也可以正常工作,改变结构体大小后,增大一些或减小一些也可以正常工作

应该是delphi的编译器在做优化时的一个bug

为避免这种莫名其妙的问题,在使用stdcall时,尽量不要使用packed record

在xe7下,测试了一下,没有这个问题

posted @ 2024-04-29 13:23  qmcode  阅读(2)  评论(0编辑  收藏  举报