x64汇编学习(6)-- main's parameters

Last Updated: 2023-10-02 14:19:01 Monday

-- TOC --

本文学习如果通过汇编获取main接口的参数。测试用C代码如下:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv){
    long a = atoi(argv[1]);
    unsigned long b = atoi(argv[2]);
    printf("%s, a+b=%lu\n", argv[0], a+b);
    return a+b;
}

不开优化,汇编:

.LC0:
        .string "%s, a+b=%lu\n"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 32
        # first parameter edi is argc
        mov     DWORD PTR [rbp-20], edi
        # second parameter rsi is argv
        # 保持8bytes对齐
        mov     QWORD PTR [rbp-32], rsi
        # copy argv to rax
        mov     rax, QWORD PTR [rbp-32]
        # move to argv[1]
        add     rax, 8
        # copy [rax] to rax
        # argv的类型是char**,
        # 因此要取两次值。
        mov     rax, QWORD PTR [rax]
        # rdi is atoi's parameter
        mov     rdi, rax
        call    atoi
        # 有符号扩展eax到rax
        cdqe
        # long a = atoi(argv[1])
        mov     QWORD PTR [rbp-8], rax
        # rax = argv
        mov     rax, QWORD PTR [rbp-32]
        # rax = argv[2]
        add     rax, 16
        # copy [rax] to rax
        mov     rax, QWORD PTR [rax]
        # rdi = argv[2]
        mov     rdi, rax
        call    atoi
        cdqe
        # unsigned long b = atoi(argv[2])
        mov     QWORD PTR [rbp-16], rax
        mov     rdx, QWORD PTR [rbp-8]
        mov     rax, QWORD PTR [rbp-16]
        # printf的第3个参数
        add     rdx, rax
        mov     rax, QWORD PTR [rbp-32]
        mov     rax, QWORD PTR [rax]
        # printf的第2个参数
        mov     rsi, rax
        # printf的第1个参数
        mov     edi, OFFSET FLAT:.LC0
        # 无浮点数
        mov     eax, 0
        call    printf
        # 取rax = a
        mov     rax, QWORD PTR [rbp-8]
        # 按int计算,只取rax的低32位
        mov     edx, eax
        # 按int计算,只取rdx的第32位
        mov     rax, QWORD PTR [rbp-16]
        add     eax, edx
        leave
        ret

不开优化,基本上就是规规矩矩的汇编。参数放入stack,取用通过寄存器中转。

argv的类型是char**,汇编代码要取两次值,才能取道最终值的地址。

-O3

.LC0:
        .string "%s, a+b=%lu\n"
main:
        # r12 needs to be preserved before using
        push    r12
        # strtol的第3个参数,base=10
        mov     edx, 10
        # rbp needs to be preserved before using
        push    rbp
        mov     rbp, rsi
        # rbx needs to be preserved before using
        # 3个push,16字节对齐了
        push    rbx
        # strtol的第1个参数,argv[1]
        mov     rdi, QWORD PTR [rsi+8]
        # strtol的第2个参数,char **endptr = NULL
        xor     esi, esi
        call    strtol
        mov     rdi, QWORD PTR [rbp+16]
        # 不要简单的认为下面两行代码是多余的!
        # 在strtol内部,使用rdx和rsi,不需要先push。
        mov     edx, 10
        xor     esi, esi
        # rbx = atoi(argv[1])
        mov     rbx, rax
        call    strtol
        mov     rsi, QWORD PTR [rbp+0]
        # 只用到了ebx,保留了atoi的风格
        movsx   rdx, ebx
        mov     edi, OFFSET FLAT:.LC0
        # 取rax的低32位
        movsx   r12, eax
        xor     eax, eax
        add     rdx, r12
        call    printf
        # 将rbx+r12的值,当作地址,
        # lea取这个地址的低32位到eax。
        lea     eax, [rbx+r12]
        # 逆序pop
        pop     rbx
        pop     rbp
        pop     r12
        ret

看来,优化不仅是速度,还有安全,源代码的atoi被直接替换成了strtol,但其返回值,依然只使用低32位。

按照Linux ABI,rbp, rbx, r12, r13, r14, r15这几个寄存器在使用之前,需要保留其值。这里用到了3个,rbp没有指向stack frame base。因此优化之后,计算的中间结果没有保存到stack中去,因此要使用这些寄存器来保存中间结果。也只有使用这些寄存器,可以确定call strtol之后,保存的值不会被改变。

本文链接:https://cs.pynote.net/hd/asm/202302111/

-- EOF --

-- MORE --