您尚未登录。

#16 2021-08-11 12:46:20

natsunoyoru97
会员
注册时间: 2021-08-06
帖子: 9
个人网站

Re: 《程序员的自我修养:链接、装载与库》演示程序运行错误

不过应该也不会是这里的问题才对!-m32就是让编译器按照System V i386的调用约定去编译程序。

嗯,我又想了想,的确(倒不如说看到用了-m32的汇编码就很怀疑自己之前的判断了
然而从开gdb看到的汇编指令中能看到,即使开了-m32运行时仍然用的是x86_64的传参方式(objdump -d ./runso得到的汇编代码也是这样)。

另外我有仔细读了一下这段代码的汇编指令,
看起来就是调用atof并将其返回值放到栈上以便传递给func这部分,被编译器直接丢弃了。

我还发现一点,运行结果正常的RunsoSimple和runso相比,多了esi、edi、edx等寄存器入栈/出栈的过程(这是x86_64的约定,显然不符合预期),我在想runso没传参数会不会和没传源变址偏移量有关系。虽然i386机器运行时不会这么做,但是在运行过程中却发生了符合x86_64标准的调用。
fNr3DA.png

最近编辑记录 natsunoyoru97 (2021-08-11 15:42:21)


像树一样自由。

离线

#17 2021-08-11 21:33:47

matrikslee
会员
注册时间: 2017-04-21
帖子: 451
个人网站

Re: 《程序员的自我修养:链接、装载与库》演示程序运行错误

哈哈哈,在确认是系统环境问题之后,重新捡起了这件事情,花了两天,终于把这个程序的double类型的问题修好了!(其他类型的代码路径我还没有验证,不知道是不是OK的)

runso_demo

主楼贴的代码问题有两个!
一个是主函数中case 'd'下面的函数指针类型定义的返回值是int,导致代码从eax寄存器拿返回值而不是用fst指令从st0寄存器拿返回值,改成

double (* func_double)() = func;

就OK了!
由于原书被我放在公司了,所以现在也不清楚到底是我手敲代码搞的乌龙,还是书上原本就是这样错的!(大写的尴尬!

另外一个是所谓的atof调用被干掉导致实际二进制代码里面没有调用atof的程序,结果也就无从谈起!
我将SETUP_STACK部分的代码中case 'd'下面的代码改成了如下样子就OK了,使用"f"类型的输入寄存器将atof函数的返回值传给内联asm部分的代码

#define SETUP_STACK \
....
    case 'd': \
        asm volatile("subl $8, %%esp\n\t" \
		"fstl (%%esp)" \
		::"f"(atof(&argv[i][1]))); \
        esp += 8; \
        break; \
...

但是在这两天的debug过程中发现另一个比较奇怪的问题,
如果代码写成书本上有问题的那样

#define SETUP_STACK \
....
    case 'd': \
        atof(&argv[i][1]); \
        asm volatile("subl $8, %esp\n\t" \
		"fstl (%%\esp)" ); \
        esp += 8; \
        break; \
...

则atof的调用会直接被编译器丢弃!
但是如果这时候简单的将atof(&argv[_i][1])换成strtod(&argv[_i][1],0);则函数调用实际是发生了的,只是函数返回的结果会直接被丢掉(通过汇编指令"fstp st(0)")不过也无所谓了,反正不管有没有发生调用,实际都没用。

所以将代码改成用输入浮点寄存器进入内联asm代码部分的操作还是必要的,这个问题即使在没有书的情况下我也可以确保书上源代码就是这样错的!

另外,debug过程中还学到了另外一个gcc 内联汇编的知识解了我之前在主楼说的疑惑,那就是少%的编译错误,其实是gcc内联汇编在有输入寄存器的情况下,访问cpu寄存器需要两个%,如果没有输入寄存器,则只要一个%,否则会有编译错误!

最后特别感谢一下@natsunoyoru97,以前根本不知道gdb可以在汇编级指令级别进行debug(囧,,楼上贴的gdb调试方法(layout asm/reg)大大提高了我的debug效率,否则估计还得调了一两天

关于楼上提到的

natsunoyoru97 说:

我还发现一点,运行结果正常的RunsoSimple和runso相比,多了esi、edi、edx等寄存器入栈/出栈的过程(这是x86_64的约定,显然不符合预期),我在想runso没传参数会不会和没传源变址偏移量有关系。

这个地方是被搞晕了吧哈哈哈,i386机器可以允许使用寄存器做返回值,但是参数是一定要放到栈上面去的,也就是说如果要传递 参数就一定得有push指令,
反之,x64环境下,调用约定允许在参数能通过寄存器传递的情况下,允许使用寄存器传递参数,所以如果有参数而无push指令,则x64下是正常的。

最近编辑记录 matrikslee (2021-08-11 21:41:39)

离线

#18 2021-08-11 21:47:24

natsunoyoru97
会员
注册时间: 2021-08-06
帖子: 9
个人网站

Re: 《程序员的自我修养:链接、装载与库》演示程序运行错误

有意思有意思,我回头试试改过的代码,再看看其它分支还能怎么改(我这边测试的结果是至少i分支结果也不对劲)。

所以现在也不清楚到底是我手敲代码搞的乌龙,还是书上原本就是这样错的!

嘛书上原本是double类型的(不过我在测试的时候改回来了),这点小问题改了就不用介意hhh

另外,debug过程中还学到了另外一个gcc 内联汇编的知识解了我之前在主楼说的疑惑,那就是少%的编译错误,其实是gcc内联汇编在有输入寄存器的情况下,访问cpu寄存器需要两个%,如果没有输入寄存器,则只要一个%,否则会有编译错误!

学到了!网上的文章说法有些太绕了,还是你的说法更直接w

这个地方是你被搞晕了吧哈哈哈,i386机器可以允许使用寄存器做返回值,但是参数是一定要放到栈上面去的,也就是说如果要传递 参数就一定得有push指令,
反之,x64环境下,调用约定允许在参数能通过寄存器传递的情况下,允许使用寄存器传递参数,所以如果有参数而无push指令,则x64下是正常的。

是这样吗,我对i386的架构不熟悉(没接触过32位的机器,平常也是在x64架构下写代码),有机会我一定会好好观察一下传递参数的行为(握拳

最近编辑记录 natsunoyoru97 (2021-08-11 22:28:55)


像树一样自由。

离线

页脚