GNU x86-64汇编简单介绍

GNUx86-64汇编

寄存器

X86-64大约有16个64位整数寄存器,其中栈指针rsp和基址指针rbp较为特殊,rsirdi跟处理字符串相关。后面的八个寄存器是编号的,使用起来没有特殊限制。

  • rax rbx rcx rdx
  • rsi rdi rbp rsp
  • r8 - r15

其中rax的结构如下
[image:ECA803C6-AECB-4593-8CAA-34CF915FBC41-86030-0001242002E5C3CA/20171008192750274.png]
rax的低八位为al,接着八位是ah,合并为ax,低32位为eax,整个64位是rax

R8的结构如下
[image:8D73982F-FE1D-4750-ADD6-9CDBEC733828-86030-0001243A3CCBAFA8/20171008193142962.png]
大多数编译器产品会混合使用32位和64位模式。32位用来做整数计算,64位一般用来保存内存地址(指针)。

寻址模式

mov指令有一个决定移动多大数据的单字母前缀

  • movb Byte 8bits
  • movw Word 16bits
  • movl Long 32bits
  • movq Quadword 64bits

直接寻址

不同的数据有不同的寻址模式
全局值和函数:直接使用名字,如printf
常数:带有美元符号的立即数,如$56
寄存器:使用寄存器名称,如%rbx

间接寻址

简介寻址是使用与寄存器保存的地址对应的内存中的值,如(%rsp)表示rsp寄存器指向的内存中的值。

相对基址寻址

表示把一个常数加到寄存器值上,例如-16(%rcx)表示把rcx指向的地址前移16个字节后对应的内存值。

寻址模式相对于管理栈空间、局部变量、函数参数很重要,相对基址寻址也有很多变种,例如-16(%rbx, %rcx, 8)表示-16+%rbx+%rcx*8对应的地址的内存值,这种寻址模式在访问元素大小特殊的数组时很有用。
下面都表示将一个值加载到rax寄存器上
[image:676C703E-273B-45BF-8548-7A17272C9F8E-86030-000124D32C54E673/20171008200403991.png]

计算

编译器会用到四个基本算数计算指令

  • ADD
  • SUB
  • IDIV
  • IMUL
    上面的三个操作都有两个操作数,目的操作数在操作以后会被改写。

ADDQ %rbx, %rax
表示rbx的值加上rax的值,写到rax内。在例如写b=b*(b+a)的时候需要注意不要把b的值覆盖了,如下

movq a, %rax
movq b, %rbx
addq %rbx, %rax
imulq %rbx
movq %rax, c

IMUL操作只有一个操作数,表示把%rax的值乘以操作数,把低64位放在%rax,高64位放在%rdx。IDIV相反,把低64位的%rax,高64位的%rdx表示的数除以操作数,商放在%rax,余数在%rdx。cdqo指令会把%rax符号扩展到%rdx

movq a, %rax
cdqo
idivq $5    # divide %rdx:%rax by 5, leaving result in %eax

INC和DEC会把寄存器的值破坏掉。例如,语句a=++b可以这样翻译:

movq b, %rax
incq %rax
movq %rax, a

布尔操作的工作方式类似,AND,OR,XOR,NOT也会破坏寄存器的值。

小贴士: 浮点数 
我们不讨论浮点数操作细节,只需要知道它们使用一套不同的指令和寄存器。在老式机器上,浮点指令是使用可选的外部8087 FPU处理的,所以被称作X87操作,虽然现在已经集成到了CPU里面。X87 FPU包含 
8个排列在栈中的80位寄存器(R0-R7)。做浮点算术前,代码必须先把数据push到FPU栈,然后操作栈顶的数据,并回写到内存。内存中双精度浮点数是以64位的长度存储的。这种架构的一个奇怪的地方是,FPU的精度是80位,比内存中的存储方式精度高。结果,浮点计算的值会改变,取决于数据在内存和寄存器之间移动的具体顺序。 
浮点数数学计算比它看上去要难懂,推荐阅读: 
1. Intel 手册8-1章节。 
2. 计算机科学家必知之浮点数 
3. 程序员必知之浮点数
--------------------- 
作者:阿威_t 
来源:CSDN 
原文:https://blog.csdn.net/pro_technician/article/details/78173777 

比较和跳转

JMP指令可以构造一个无限循环,%eax开始计数

    movq $0, %rax
loop:
    incq %rax
    jump loop

所有的比较都用CMP指令,指令比较两个不同的寄存器中的值,设置eflag寄存器的比特位,记录下结果,jump指令集会利用eflag寄存器中的结果进行跳转
[image:A28DA285-C819-4485-BBCF-63999A382B76-86030-00012629E8112A97/20171013211645843.png]
下面是一个从0累加到5的循环

    movq $0, %rax
loop:
    incq %rax
    cmp $5, %rax
    jle loop

设置y的值,如果x大于0,y=10,否则为20

    movq x, %rax
    cmpq $0, %rax
    jle twenty
ten:
    movq $10, %rbx
    jmp done
twenty:
    movq $20, %rbx
    jmp done
done:
    movq %rbx, y

注意:上面的ten/twenty/done都是标签,标签在一个汇编文件中私有,对外部不可见,除非有.globl标志。c语言的说法,汇编中没有修饰的标签是static的,.globl修饰的标签是extern的。

堆栈

一般内存有如下结构

|----内存高位----|
|--------------|
|--------------|<-------栈底
|--------------|
|--------------|(栈空间向下增长)
|--------------|
|--------------|<-------栈顶
|--------------|
|--------------|
|--------------|<-------堆顶
|--------------|
|--------------|(堆空间向上增长)
|--------------|
|--------------|<-------堆底
|--------------|
|----内存低位----|

函数调用会将参数压入栈中,等调用完后再恢复栈结构,完成一次调用。
%rsp栈指针,指向栈顶,压栈的操作是将%rsp减去8字节,预留出64位,并把%rax写到%rsp指向的内存空间。

subq $8, %rsp
movq %rax, (%rsp)

等价于

pushq %rax

Pop刚好相反

movq (%rsp), %rax
addq $8, %rsp

等价于

popq %rax

如果想丢弃栈中的值,只需要增加%rsp的值

addq $8, %rsp

函数调用

X86-64的函数堆栈System V ABI较为复杂,这里只做简单的介绍

  • 整形参数(和指针)以此放在%rdi, %rsi, %rdx, %rcx, %8, %9寄存器中
  • 浮点参数依次放在%xmm0-%xmm7中
  • 寄存器不够用时,参数放在栈中
  • 可变参数(printf),寄存器%eax需要记录下有多少个浮点参数的个数
  • 被调用的函数可以使用任何寄存器,但必须保证%rbx, %rbp, %rsp和%r12-%15恢复到原来的值
  • 返回值放在%eax中
    [image:E96C7FBD-E6FF-405C-8372-3C74672A64E3-86030-00012E397428F584/20171015115531621.png]
    函数调用前,需要先把参数放到寄存器中,将%r10和%r11的值保存到栈中,之后执行call指令,把IP指针的值保存到栈中,然后跳转执行,从函数恢复后,恢复%r10和%r11的值,并从%eax中获取返回值。
long x=0;
long y=10;
int main()
{
    x = printf("value: %d", y);
}

对应的汇编

.data
x:
    .quad 0
y:
    .quad 10
str:
    .string "value: %d"

.text
.globl main
main:
    movq $str, %rdi
    movq y, %rsi
    movq $0, %eax #没有浮点数
    pushq %r10
    pushq %r11
    
    call printf
    
    popq %r11
    popq %r10
    
    movq %rax, x
    ret
long square(long x)
{
    return x*x;
}
.globl square
square:
    movq %rdi, %rax
    imulq %rdi, %rax
    ret

一个复杂函数的调用都有如下步骤

  1. 改变栈底值
  2. 将参数依次压入栈中
  3. 预留函数调用的local variables的空间
  4. 保护好原有的寄存器rbx, r12-r15
  5. 函数调用
  6. 恢复原有的寄存器
  7. 恢复栈底
.globl func
func:
    pushq %rbp          # save the base pointer
    movq  %rsp, %rbp    # set new base pointer

    pushq %rdi          # save first argument on the stack
    pushq %rsi          # save second argument on the stack
    pushq %rdx          # save third argument on the stack

    subq  $16, %rsp     # allocate two more local variables

    pushq %rbx          # save callee-saved registers
    pushq %r12
    pushq %r13
    pushq %r14
    pushq %r15

    ### body of function goes here ###

    popq %r15            # restore callee-saved registers
    popq %r14
    popq %r13
    popq %r12
    popq %rbx

    movq   %rbp, %rsp    # reset stack to previous base pointer
    popq   %rbp          # recover previous base pointer
    ret                  # return to the caller

%rbp和%rsp之间的内存缴存stack frame也叫做活动记录。
下面是func内部的栈内存布局。
[image:D59227A1-B882-4E26-B2E1-1926878C9833-86030-00012F66B9789F54/20171015133500035.png]
%rbp指明了栈帧的开始。在函数体内,我们可以用%rbp基址相对寻址方式来引用参数和局部变量。参数0在 -8(%rbp)位置,参数1在 -16(%rbp),以此类推。 -32(%rbp) 对应局部变量,-48(%rbp)对应保存的寄存器。%rsp指向栈中最后一个元素。如果栈还要另作他用,则需要向更低地址的区域压栈。(注意:我们假设所有参数和变量都是8字节长度, 实际上不同的类型的长度不一样,对应的偏移也不一样)。

下面是一个真实的汇编

#include <stdio.h>
int sum(int a, int b)
{
    return a+b;
}
int main()
{
    int x=10;
    int y=20;
    printf("sum is:%d\n", sum(x,y));
    return 0;
}
        .globl  __Z3sumii               ## -- Begin function _Z3sumii
__Z3sumii:                              ## @_Z3sumii
        .cfi_startproc
        pushq   %rbp
        movq    %rsp, %rbp
        movl    %edi, -4(%rbp)
        movl    %esi, -8(%rbp)
        movl    -4(%rbp), %esi
        addl    -8(%rbp), %esi
        movl    %esi, %eax
        popq    %rbp
        retq
        .cfi_endproc
                                        ## -- End function
        .globl  _main                   ## -- Begin function main
_main:                                  ## @main
        .cfi_startproc
        pushq   %rbp #保存栈底
        movq    %rsp, %rbp #将栈顶用作新的栈底,保存旧栈帧
        subq    $16, %rsp  #预留4个字节作为栈大小
        movl    $0, -4(%rbp) #0压栈,我也不知道为什么
        movl    $10, -8(%rbp)#两个变量压栈
        movl    $20, -12(%rbp)
        movl    -8(%rbp), %edi#将值写入edi/esi寄存器,准备调用
        movl    -12(%rbp), %esi
        callq   __Z3sumii
        leaq    L_.str(%rip), %rdi
        movl    %eax, %esi#记录返回值到esi
        movb    $0, %al
        callq   _printf
        xorl    %esi, %esi
        movl    %eax, -16(%rbp)#保存结果         ## 4-byte Spill
        movl    %esi, %eax
        addq    $16, %rsp #恢复栈顶
        popq    %rbp #恢复栈底
        retq
        .cfi_endproc
                                        ## -- End function
L_.str:                                 ## @.str
        .asciz  "sum is:%d\n"

深入浅出GNU X86-64 汇编 - pro_technician的专栏 - CSDN博客

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 这是程序栈话题的最后一篇,可能有人会问,你前面5篇写那么多x86程序栈的文章干什么?请耐心看下去,即便现在x64硬...
    铁甲万能狗阅读 1,574评论 0 0
  • 本文首发于我的博客 Bomb Lab 实验代码见GitHub 简介 BombLab是CS:APP中对应第三章内容:...
    viseator阅读 14,219评论 0 14
  • x86汇编指令集包括x86-64(intel-64,amd64, emt64), x86-32, x86-16 内...
    bitzoo阅读 3,613评论 0 1
  • 0. 引言 如果你学的第一门程序语言是C语言,那么下面这段程序很可能是你写出来的第一个有完整的 “输入---处理-...
    pandolia阅读 13,950评论 13 27
  • 由于不是科班出生,又是自学开发,对很多方面的知识都是只知其然而不知其所以然。加上最近公司事情不多,刚好乘此机会把长...
    寒咯阅读 12,929评论 3 8