[笔记] x86/x64编程基础

内容来自:x86/x64体系探索及编程 (豆瓣) (douban.com) 第二章 - x86/x64编程基础

1 选择编译器

nasm、fasm、yasm 是免费开源的汇编编译器,总体上来讲都使用 Intel 的语法。yasm 是在 nasm 的基础上开发的,与 nasm 同宗。由于使用了相同的语法,因此 nasm 的代码可以直接用 yasm 来编译。

2 机器指令

一条机器指令由相应的二进制数标识,直接能被机器识别。

C 语言中的 c = a + b 在机器语言中应该怎样表达?

首先用相应的汇编语言表达出来。

asm 复制代码
mov eax, [ebp-4]        ; 变量 a 的值放到 eax 寄存器中,变量 a 是局部变量
add eax, [ebp-8]        ; 执行 a + b,变量 b 也是局部变量
mov [0x0000001c], eax   ; 放到 c 中,变量 c 可能是外部变量

在 x86 机器中,如果两个内存操作数要进行加法运算,不能直接相加,其中一方必须是寄存器。

上面的汇编语言译成机器语言为:

asm 复制代码
8b 45 fc            ; 对应于 mov eax, [ebp-4]
03 45 f8            ; 对应于 add eax, [ebp-8]
a3 1c 00 00 00      ; 对应于 mov [0x0000001c]
eax

3 Hello world

"Hello, World" 程序的汇编版。

asm 复制代码
main: ; 这是模块代码的入口点。

    mov si, caller_message
    call puts ; 打印信息

    mov si, current_eip
    mov di, caller_address

current_eip:
    call get_hex_string ; 转换为 hex
    mov si, caller_address
    call puts
    mov si, 13 ; 打印回车
    call putc
    mov si, 10 ; 打印换行
    call putc
    call say_hello ; 打印信息
    jmp $

caller_message db 'Now:I am the caller,address is 0x'
caller_address dq 0
hello_message db 13,10,'hello,world!',13,10 db 'This is my first assembly program...',13,10,13,10,0
callee_message db "Now:I'm callee - say_hello(),address is 0x"
callee_address dq 0

这段汇编代码相当于下面的几条 C 语言语句。

c 复制代码
int main()
{
    print("new: I am the caller, address is 0x%x", get_hex_string(current_eip));
    printf("\n");
    say_hello(); // 调用 say_hello()
}

下面是 say_hello() 的汇编代码。

asm 复制代码
say_hello:
    mov si, hello_message
    call puts
    mov si, callee_message
    call puts
    mov si, say_hello
    mov di, callee_address
    call get_hex_string
    mov si, callee_address
    call puts
    ret

这个 say_hello() 相当于以下几条 C 语言。

c 复制代码
void say_hello()
{
    printf("hello, workd\nThis is my first assembly progra...");
    printf("Now: I'm callee -say_hello(), address is 0x%x", get_hex_string(say_hello);
}

3.1 使用寄存器传递参数

在汇编程序里尽量采用寄存器传递参数,使用寄存器传递参数获得更高的效率。

asm 复制代码
mov si, hello_message ; 使用 si 寄存器传递参数
call puts

3.2 调用过程

call 指令用来调用一个过程,可以直接出一个目标地址值作为操作数。

asm 复制代码
e8 c2 00        ; call puts

3.3 定义变量

在 nasm 的汇编源程序里,可以使用 db 系列伪指令来定义初始化的变量。

例如,我们可以这样使用 db 伪指令。

asm 复制代码
hello_message        db 13, 10, 'hello, world!', 13, 10

这里为 hello_message 定义了一个字符串变量,相当于如下 c 语句。

c 复制代码
hello_message[] = "\nhello, world!\n";

十进制数字13是 ASCII 码中的回车符,10是换行符,当然也可以使用十六进制数0x0d和0x0a来赋初值。

4 16位编程、32位编程,以及64位编程

在 nasm 中可以在同一个源代码文件里同时指出16位代码、32位代码,以及64位代码。

asm 复制代码
bits 16
... ... ; 以下是 16位代码
bits 32
... ... ; 以下是 32位代码
bits 64
... ... ; 以下是 64位代码

4.1 通用寄存器

在16位和32位编程里,可以使用的通用寄存器是一样的。

4.2 操作数大小

在16位编程和32位编程下,寄存器没有使用上的不便,32位的操作数依旧可以在16位编程里使用,而16位的操作数也可以在32位编程下使用。

4.3 64位模式下的内存地址

在64位编程里可以使用宽达64位的地址值。

4.4 内存寻址模式

在16位和32位编程里,16位和32位的寻址模式都可以使用。在64位下,32位的寻址模式被扩展为64位,而且不能使用16位的寻址模式。

5 编程基础

一行有效的汇编代码主体是 instruction expression(指令表达式),label(标签)定义了一个地址,汇编语言的 comment(注释)以";"号开始,以行结束为止。

5.1 操作数寻址

5.1.1 寄存器寻址

用户编程中几乎只使用 GPR(通用寄存器),sp/esp/rsp 寄存器被用作 stack top pointer(栈顶指针),bp/ebp/rbp 寄存器通常被用作维护过程的 stack frame 结构。可是它们都可以被用户代码直接读/写,维护 stack 结构的正确性和完整性,职责在于程序员。

5.1.2 内存操作数寻址

内存操作数由一对[]括号进行标识。

直接寻址是 memory 的地址值明确提供的,是个绝对地址。

asm 复制代码
mov eas, [0x00400000]        ; 明确提供一个地址值

间接寻址 memory 的地址存放在寄存器里,或者需要进行求值。

asm 复制代码
mov eax, [ebx]                    ; 地址值放在 ebx 寄存器里
mov eax, [base_address + ecx * 2] ; 通过求值得到地址值

5.1.3 立即数寻址

立即数无需进行额外的寻址,immediate 值将从机器指令中获取。

asm 复制代码
b8 01 00 00 00       ; 对应 mov eax, 1

5.1.4 I/O 端口寻址

x86/x64 体系实现了独立的 64K I/O 地址空间(从 0000H 到 FFFFH),IN 和 OUT 指令用来访问这个 I/O 地址。

in 指令读取外部端口数据,out 指令往外部端口写数据。

asm 复制代码
in al,20H ; 从端口20H里读取一个 byte

5.2 传送数据指令

x86提供了非常多的 data-transfer 指令,在这些传送操作中包括了:load(加载),store(存储),move(移动)。其中,mov 指令是最常用的。

5.3 位操作指令

x86 也提供了几类位操作指令,包括:逻辑指令,位指令,位查询指令,位移指令。

5.4 算术指令

  1. 加法运算:ADD,ADC,以及INC指令。
  2. 减法运算:SUB,SBB,以及DEC指令。
  3. 乘法运算:MUL和IMUL指令。
  4. 除法运算:DIV和IDIV指令。
  5. 取反运算:NEG指令。

5.5 CALL 与 RET 指令

CALL 调用子过程,在汇编语言里,它的操作数可以是地址(立即数)、寄存器或内存操作数。call 指令的目的是转入目标代码的 IP(Instruction Pointer)值。

为了返回到调用者, call 指令会在 stack 中压入返回地址,ret 指令返回时从 stack 里取出返回值重新装载到 EIP 里然后返回到调用者。

5.6 跳转指令

jmp 系列指令与 call 指令最大的区别是:jmp 指令并不需要返回,因此不需要进行压 stack 操作。

相关推荐
唐诺21 小时前
几种广泛使用的 C++ 编译器
c++·编译器
喵~来学编程啦1 天前
【编译原理】编译原理知识点汇总·词法分析器(正则式到NFA、NFA到DFA、DFA最小化)
学习笔记·编译原理
喵~来学编程啦3 天前
【编译原理】编译原理知识点汇总·概论与文法
学习笔记·编译原理
luoganttcc9 天前
【编译器】传统编译器和AI/ML编译器总结
人工智能·编译器
Crossoads9 天前
【汇编语言】内中断(二) —— 安装自己的中断处理程序:你也能控制0号中断
android·开发语言·数据库·人工智能·深度学习·机器学习·汇编语言
Crossoads21 天前
【汇编语言】标志寄存器(一) —— 标志寄存器中的标志位:ZF、PF、SF、CF、OF 一网打尽
android·开发语言·数据库·人工智能·深度学习·机器学习·汇编语言
同志啊为人民服务!21 天前
深入理解计算机系统,源码到可执行文件翻译过程:预处理、编译,汇编和链接
汇编·预处理·链接·编译·汇编语言·高级语言·可执行文件装载过程
Crossoads1 个月前
【汇编语言】更灵活的定位内存地址的方法(三)—— 不同的寻址方式的灵活应用
android·开发语言·数据库·人工智能·机器学习·数据挖掘·汇编语言
Crossoads1 个月前
【汇编语言】更灵活的定位内存地址的方法(二)—— 从 [bx+idata] 到 [bx+si+idata]:让你灵活的访问内存
android·java·服务器·网络协议·tcp/ip·机器学习·汇编语言
Crossoads1 个月前
【汇编语言】[BX]和loop指令(四)—— 汇编语言中的段前缀与内存保护:原理与应用解析
android·java·开发语言·数据库·机器学习·汇编语言