公用帐号 发表于 2008-04-30 17:30

x86-64的calling convention,和ia32有什么大区别? (汇编程序问题)

/* filename: ccpuid.s使用C库的printf和exit的cpupid.s
*
* $ as <-gstabs+> -o ccpuid.o ccpuid.s
* $ ld -o ccpuid ccpuid.o -lc -dynamic-linker /lib64/ld-linux.so.2
* $ ./ccpuid
*/


/* 一个不需要链接其他 *.o 的*.s文件,必须定义.data、.text这两个section
* 另外,.bss这个section可选。 必须定义_start,这是程序的第一条指令开始
* 的地方 -- 除非ld时用-e <symbol>指定别的符号为入口地址。 又,_start
* 必须用.globl这个directive指定为全局符号
*/
.section .data
output:
    /* .asciz 定义C风格的ASCII字符串,也就是末尾有个'\0' */
    .asciz "The CPU Vendor is %s\n"

.section .bss
    /* 在BSS段中定义一个buffer,其大小是12个字节 */
    .lcomm buffer, 12
   
.section .text
.globl _start   /* 定义_start符号为全局符号 */
_start:
    nop
    /* %eax清零,来执行cpuid指令 */
    xorl   %eax, %eax
    cpuid

    /* vendor ID字符串包含在ebx-edx-ecx中 */
    movq   $buffer, %rdi
    movl      %ebx, (%rdi)
    movl      %edx, 4(%rdi)
    movl      %ecx, 8(%rdi)

    /* 调用printf("The CPU Vendor is %s\n", buffer) */
    pushq   $buffer
    pushq   $output
    call      printf
    /* 清除传给printf的参数 */
    addq      $16, %rsp

    /* 调用void _exit(int status) */
    pushq   $0
    call      exit




输出:
$ ./ccpuid
AuthenticAMD$



在gdb中, 调用printf之前和之后, 查看$output的内存:

(gdb) x/30c &output
0x600420 <output>:      84 'T'104 'h' 101 'e' 32 ' '67 'C'80 'P'85 'U'32 ' '
0x600428 <output+8>:    86 'V'101 'e' 110 'n' 100 'd' 111 'o' 114 'r' 32 ' '105 'i'
0x600430 <output+16>:   115 's' 32 ' '37 '%'115 's' 10 '\n' 0 '\0'0 '\0'0 '\0'
0x600438 <buffer>:      65 'A'117 'u' 116 't' 104 'h' 101 'e' 110 'n'

没问题呀, 为什么好象没传给printf呢?

mik 发表于 2008-04-30 18:10

原帖由 公用帐号 于 2008-4-30 17:30 发表 http://linux.chinaunix.net/bbs/images/common/back.gif
/* filename: ccpuid.s使用C库的printf和exit的cpupid.s
*
* $ as-o ccpuid.o ccpuid.s
* $ ld -o ccpuid ccpuid.o -lc -dynamic-linker /lib64/ld-linux.so.2
* $ ./ccpuid
*/


/* 一个不需 ...

这由于 x64 的寻址模式所致,x64 的 imme 操作数除了 GPRs 外一律都不能是 64 位,

说明白点,就是:
   除了 mov rax, 0x1122334455667788、 mov rdx, 0x1122334455667788 之外,所有的 imme 操作数最大只能是 32 位。
   像: movqword ptr , 0x1122334455667788 这条指令结果只能是:截断 imme 且符号扩展为64位,
         等于:mov qword ptr , 0x0000000055667788


PUSH 在 64 位下,所有的 PUSH imme 都是 64 位的,结果只像 mov ,imme 一像截断 imme 且符号扩展为 64 位。
所以: pushq $buffer   这条指令的结果可能并不是你想要的。


要这么做:

movq $buffer, %rax
movq $output, %r8
pushq %rax
pushq $r8
.... ...


:mrgreen:

[ 本帖最后由 mik 于 2008-4-30 18:15 编辑 ]

mik 发表于 2008-04-30 18:26

回复 #1 公用帐号 的帖子

思考了一下,貌似即使 64 位符号扩展应该没问题的,大多数代码不会用到0x80000000以上的区域的吧

我上面说的方法你试试看行不行。不行的话把代码 objdump 贴出来再分析分析。看看是不是调用 printf 时的编码问题

[ 本帖最后由 mik 于 2008-4-30 18:27 编辑 ]

mik 发表于 2008-05-01 08:55

LZ:

不是imme被截断的原因,原因是:64位下是用寄存器传递参数。
偶也是习惯32位的编程模式,一时忘了64位编程的变化。渐愧:mrgreen:

你了解下 64 位的ABI规定:

1、7个通用寄存器(rdi,rsi,rdx,rcx,r8,r9 和 rax)依次用作函数传递参数。
2、rsp 及 rbp 用于管理堆栈
3、r10 及 r11 用于临时寄存器
4、5个通用寄存器(r12,r13,r14,r15 及 rbx)由被调用方保存


这里有一份 AMD的 ABI:

albcamus 发表于 2008-05-19 12:25

原帖由 mik 于 2008-5-1 08:55 发表 http://linux.chinaunix.net/bbs/images/common/back.gif
LZ:

不是imme被截断的原因,原因是:64位下是用寄存器传递参数。
偶也是习惯32位的编程模式,一时忘了64位编程的变化。渐愧:mrgreen:

你了解下 64 位的ABI规定:

1、7个通用寄存器(rdi,rsi,rdx,rc ...

老大,这个帖子是我发的。

请问你是从哪里找到的这份abi文档?我在AMD的Mnauals/Developer Guides/Revision Guides这三部分的文档中都没有找到它。

谢谢:em02:

bradpitt88 发表于 2008-05-29 15:53

版主,能不能把修改后的所有源代码贴出来?

albcamus 发表于 2008-05-30 12:48

原帖由 bradpitt88 于 2008-5-29 15:53 发表 http://linux.chinaunix.net/bbs/images/common/back.gif
版主,能不能把修改后的所有源代码贴出来?

当然可以:)


/* filename: ccpuid.s使用C库的printf和exit的cpupid.s
*
* for x86-64
*
* $ as <-gstabs+> -o ccpuid.o ccpuid.s
* $ ld -o ccpuid ccpuid.o -lc -dynamic-linker /lib64/ld-linux.so.2
* $ ./ccpuid
* The CPU Vendor is AuthenticAMD
* $ echo $?   //打印上一个子进程的退出值
* 0
*/


/* 一个不需要链接其他 *.o 的*.s文件,必须定义.data、.text这两个section
* 另外,.bss这个section可选。 必须定义_start,这是程序的第一条指令开始
* 的地方 -- 除非ld时用-e <symbol>指定别的符号为入口地址。 又,_start
* 必须用.globl这个directive指定为全局符号
*/
.section .data
output:
      /* .asciz 定义C风格的ASCII字符串,也就是末尾有个'\0' */
      .asciz "The CPU Vendor is %s\n"

.section .bss
      /* 在BSS段中定义一个buffer,其大小是12个字节 */
      .lcomm buffer, 12
            
.section .text
.globl _start   /* 定义_start符号为全局符号 */
_start:
      nop
      /* %eax清零,来执行cpuid指令 */
      xorl    %eax, %eax
      cpuid

      /* vendor ID字符串包含在ebx-edx-ecx中 */
      movq    $buffer, %rdi
      movl    %ebx, (%rdi)
      movl    %edx, 4(%rdi)
      movl    %ecx, 8(%rdi)

      /* 调用printf("The CPU Vendor is %s\n", buffer)
         * 注意,x86-64的calling cenvention是:参数从左到右
         * 依次为:rdi, rsi, rdx, rcx, r8, r9和rax这7个通用
         * 寄存器
         */
      movq    $output, %rdi
      movq    $buffer, %rsi
      call    printf

      /* 调用void _exit(int status) */
      movq    $0, %rdi
      call    exit

bradpitt88 发表于 2008-05-30 22:43

版主,我还有一个问题,我分别用as和ld编译、链接生成的程序运行起来没有问题,但是,我使用gcc -o cpuid cpuid.s来生成的可执行文件运行起来却会出现“段错误”的提示。我想了很久也没有弄明白。麻烦你给解释一下!谢谢!顺便说一下,我的系统是64位的ubuntu8.04。

[ 本帖最后由 bradpitt88 于 2008-5-30 22:45 编辑 ]

albcamus 发表于 2008-06-02 12:39

原帖由 bradpitt88 于 2008-5-30 22:43 发表 http://linux.chinaunix.net/bbs/images/common/back.gif
版主,我还有一个问题,我分别用as和ld编译、链接生成的程序运行起来没有问题,但是,我使用gcc -o cpuid cpuid.s来生成的可执行文件运行起来却会出现“段错误”的提示。我想了很久也没有弄明白。麻烦你给解释一 ...

gcc编译有两个问题。


第一, ld寻找的程序入口是_start符号,这也是我们的程序中定义了的。 而gcc寻找的入口是main符号,这个符号,我们的程序中没有定义;


第二, gcc在编译程序, 到连接那一步时, 会默认去连接一些*.o文件, 这些*.o文件里是常用的历程和符号。 用gcc -v |grep collect2可以看到即使编译一个最小的程序, gcc也会默认连接上很多*.o文件。

段错误问题可能是和这些*.o文件有关, 因为这个程序不是C程序,不需要那些*.o文件, 而gcc连接了。

bradpitt88 发表于 2008-06-02 13:05

版主,我用gcc编译的汇编程序的入口已经使用了main,在32位的机子上,用as、ld编译链接和使用gcc编译的程序运行起来都没有任何的问题(当然,32位机子上的代码是略有不同的),只有64位的机子上才会出现我说的以上问题。不知道有没有什么办法解决呢。

版主,本人用一个简单的程序试过,在64位的机子上用as、ld生成的程序运行没有问题,但换成了gcc来生成程序运行就会出现“instruction fault“的提示。下面是程序代码(请留意一下注释的部分):
.section .data
msg:
       .asciz"Hello, ubuntu! \n"
.section .text
.globl main   #用as、ld生成程序时要把main改成_start
main:
                movq $msg, %rdi
                pushq %rdi
                call printf          #如果把这句话注释起来,在64位机子上用gcc生成的程序运行
                           #起来一切正常,但是加上这句话后在64位机子上用gcc生成的
                           #程序运行起来就会出错,问题出在call printf这个语句上
                movq $0, %rdi
                pushq %rdi
                call exit

[ 本帖最后由 bradpitt88 于 2008-6-2 13:39 编辑 ]
页: [1] 2
查看完整版本: x86-64的calling convention,和ia32有什么大区别? (汇编程序问题)