[toc]

GCC编译器的基本使用

gcc的编译过程

  1. 预处理。
1
gcc -E main.c -o main.i
  1. 编译。用编译器将预处理代码转换为汇编代码
1
gcc -S main.i -o main.s
  1. 汇编。用汇编器将汇编代码转换为机器码,产生的文件叫做目标文件
1
2
gcc -c main.s -o main.o
#as -c main.s -o main.o
  1. 链接。链接过程使用链接器将该目标文件与其他目标文件、库文件、启动文件等链接起来生成可执行文件
1
2
gcc main.o -o main
#ld main.o -o main

GCC编译器编译c++

gcc/g++ 编译c/cpp的区别(十分啰嗦,十分详细)

1
gcc -s main.cpp -o main.exe -lstdc++

mingw64位编译32位c程序命令

1
gcc -m32 main.c -o main.out

gcc编译32位c++程序命令

1
gcc -m32 test.cpp -o test.out

gcc编译64位c++程序命令

1
gcc -m64 test.cpp -o test.out

报错的话需要安装相关库

1
2
3
4
#For C language:
sudo apt-get install gcc-multilib
#For C++ language:
sudo apt-get install g++-multilib

How to compile 32-bit program on 64-bit gcc in C and C++

静态编译命令

1
-static-libstdc++ -static-libgcc

gcc编译裸机程序

1
2
3
4
5
6
#编译
gcc -ffreestanding -c function.c -o function.o
#gcc -c function.c -o function.o
#链接
ld -o function.bin -Ttext 0x0 --oformat binary function.o
#ld -o function.bin function.o

16位实模式汇编程序

1
2
3
4
5
6
7
8
9
10
#汇编
as --32 code16.s -o code16.o
#链接
ld -Ttext 0x100 -m elf_i386 code16.o -o code16.elf
#转换
objcopy -j .text -O binary code16.elf code16.com
#8086反汇编
objdump -d -m i8086 code16.elf
#binary反汇编
objdump -D -m i8086 -b binary code16.com

gcc内连汇编

1
2
#指定intel格式
gcc -masm=intel test.c -o test

GCC编译动态库

生成动态库文件

1
gcc --shared func.c -o libfunc.so

动态库编译进代码

1
2
3
4
gcc main.c -lfunc -L. -o main
# -lfunc 是指定的依赖动态库名称func
# -L. 是指定动态库的路径
# . 当前路径

GCC编译静态库

生成静态库

1
2
3
4
#生成目标代码
gcc -c func.c -o func.o
# 打包静态库
ar -r libfunc.a func.o

将静态库编译进代码

1
gcc libfunc.a main.c -L. -o main

mingw中的汇编命令用法

将c语言源码转为汇编源码

1
gcc  -O2 -S main.c -m32 -fno-omit-frame-pointer

汇编器(assembler)汇编 AT&T 格式的源代码:

1
as -c main.s -o main.o

相关链接:

汇编语言–Linux 汇编语言开发指南

Archived | Linux assemblers: A comparison of GAS and NASM

gcc生成intel语法的汇编代码

1
2
3
gcc -S -masm=intel test.c
gcc -O2 -S -masm=intel test.c -m32 -fno-omit-frame-pointer
gcc -S -fno-asynchronous-unwind-tables -masm=intel test.c

汇编命令

1
2
3
4
#gcc -s test.s -o test.o
#nasm -f elf test.s
as test.s -o test.o
gcc test.o -o test

NASM汇编器

NASM使用的是 Intel 的汇编格式:

1
$ nasm -f elf hello.asm

使用以下命令将汇编代码编译成可执行文件:

linux64位

在 Linux 64 位环境下,代码需要进行两个核心调整:

  1. 寄存器传参规则:从 32 位的 eax, ebx, ecx, edx 变为 64 位的 rax, rdi, rsi, rdx
  2. 中断方式:从 int 80h 变为 syscall 指令,并且系统调用号也会改变(write 从 4 变为 1,exit 从 1 变为 60)。

1.编译命令

1
2
nasm -f elf64 hello.asm -o hello.o
ld hello.o -o hello

2.64位代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
section .data
hello: db 'Hello world!', 10 ; 字符串内容,10 是换行符 (\n)
helloLen: equ $ - hello ; 计算字符串长度

section .text
global _start

_start:
; --- sys_write (rax=1) ---
mov rax, 1 ; 64位中 sys_write 的调用号是 1
mov rdi, 1 ; 参数 1 (rdi): 文件描述符 1 (stdout)
mov rsi, hello ; 参数 2 (rsi): 字符串的地址
mov rdx, helloLen ; 参数 3 (rdx): 字符串的长度
syscall ; 触发系统调用

; --- sys_exit (rax=60) ---
mov rax, 60 ; 64位中 sys_exit 的调用号是 60
xor rdi, rdi ; 参数 1 (rdi): 退出状态码码 0 (表示成功)
syscall ; 触发系统调用

linux32位

1.编译命令

1
2
3
nasm -f elf32 hello.asm -o hello.o
ld -m elf_i386 hello.o -o hello
./hello

2.32位代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
section .data
hello: db 'Hello world!',10 ; 'Hello world!' plus a linefeed character
helloLen: equ $-hello ; Length of the 'Hello world!' string

section .text
global _start

_start:
mov eax,4 ; The system call for write (sys_write)
mov ebx,1 ; File descriptor 1 - standard output
mov ecx,hello ; Put the offset of hello in ecx
mov edx,helloLen ; helloLen is a constant, so we don't need to say
; mov edx,[helloLen] to get it's actual value
int 80h ; Call the kernel
mov eax,1 ; The system call for exit (sys_exit)
mov ebx,0 ; Exit with return "code" of 0 (no error)
int 80h;

linux16位

linux中运行

1.编译命令

1
2
3
4
5
6
7
8
# 编译为 32 位 ELF 对象文件(兼容 16 位指令)
nasm -f elf32 hello.asm -o hello.o

# 链接为可执行文件
ld -m elf_i386 hello.o -o hello

# 运行
./hello

2.代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
; hello.asm - 16位 Linux 汇编程序
[BITS 16] ; 告诉 NASM 这是 16 位代码

section .text
global _start

_start:
; --- 使用 sys_write (系统调用号 4) 打印字符串 ---
mov ax, 4 ; sys_write 的调用号
mov bx, 1 ; 文件描述符 1 (stdout)
mov cx, msg ; 字符串的偏移地址
mov dx, msg_len ; 字符串长度
int 0x80 ; 触发内核中断

; --- 使用 sys_exit (系统调用号 1) 退出程序 ---
mov ax, 1 ; sys_exit 的调用号
xor bx, bx ; 返回状态码 0
int 0x80 ; 触发内核中断

section .data
msg db 'Hello from 16-bit world!', 0xa
msg_len equ $ - msg

qemu运行或者裸机引导

  1. 编译命令

    1
    2
    #NASM汇编为bin文件
    nasm hello.asm -f bin -o hello.bin
  2. 编写代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    ; boot.asm - 16位裸机引导程序
    [BITS 16] ; 编写 16 位代码
    [ORG 0x7C00] ; 告知编译器程序将被加载到 0x7C00 地址

    start:
    ; --- 设置段寄存器 ---
    mov ax, 0
    mov ds, ax
    mov es, ax

    ; --- 使用 BIOS 中断打印字符串 ---
    mov si, msg ; 将字符串地址存入 SI
    call print_string

    jmp $ ; 无限循环,防止 CPU 继续执行随机内存

    print_string:
    mov ah, 0x0E ; BIOS 终端写字符功能 (Teletype Output)
    .loop:
    lodsb ; 从 [ds:si] 加载一个字节到 AL,并 SI++
    cmp al, 0 ; 检查是否为字符串结束符 0
    je .done
    int 0x10 ; 调用 BIOS 第 10h 号中断
    jmp .loop
    .done:
    ret

    section .data
    msg db 'Hello from Bare Metal!', 0

    ; --- 填充剩余空间 ---
    ; 引导扇区必须正好是 512 字节
    times 510-($-$$) db 0 ; 用 0 填充直到第 510 字节
    dw 0xAA55 ; 最后两个字节必须是 0xAA55(启动魔数)
  3. 使用 QEMU 虚拟机

    1
    qemu-system-x86_64 -drive format=raw,file=boot.bin
  4. 方法 B:写入 U 盘并在真机启动

  5. 插入 空U 盘,确定其设备名(如 /dev/sdb)。

  6. 使用 dd 命令写入:

    1
    sudo dd if=boot.bin of=/dev/sdb bs=512 count=1
  7. 重启电脑,在 BIOS 中选择从该 U 盘启动(需开启 Legacy Support/CSM 模式,不支持纯 UEFI)。

Linux aarm64上交叉编译linux64位程序

1.编译命令

1
2
3
4
5
6
7
8
9
# 安装工具链 (以 Ubuntu 为例)
sudo apt update
sudo apt install binutils-x86-64-linux-gnu

# 1. NASM 生成 64 位 x86 文件
nasm -f elf64 hello.asm -o hello.o

# 2. 使用交叉链接器
x86_64-linux-gnu-ld hello.o -o hello

2.qemu运行

1
2
3
4
5
6
7
sudo apt update
sudo apt install qemu-user
# 假设你的可执行文件名是 hello
qemu-x86_64 ./hello
#需要指定库路径。通常做法是:
qemu-x86_64 -L /usr/x86_64-linux-gnu ./hello

3.查看文件格式

1
file ./hello

如果输出包含 ELF 64-bit LSB executable, x86-64,那么使用 qemu-x86_64 运行就是正确的操作。

linux aarm64交叉编译运行windows x86_64架构程序

1. 安装交叉工具链

在 Debian/Ubuntu ARM64 上执行:

1
2
sudo apt update
sudo apt install nasm binutils-mingw-w64-x86-64 gcc-mingw-w64-x86-64

2. 编写代码 (Windows x64 语法)

保存为 win.asm。注意 Windows 的调用约定(前四个参数在 rcx, rdx, r8, r9,且需要影子空间)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
; win.asm
extern printf
extern ExitProcess

section .data
msg db 'Hello from ARM64 Linux to Windows x64!', 10, 0

section .text
global main
main:
sub rsp, 40 ; 预留影子空间 + 对齐
lea rcx, [rel msg] ; 参数 1: 字符串地址 (使用相对寻址)
call printf
xor rcx, rcx ; 参数 1: 退出码 0
call ExitProcess

3. 交叉编译与链接

1
2
3
4
5
# 1. 使用 nasm 编译成 win64 目标文件
nasm -f win64 win.asm -o win.obj
# 2. 使用交叉链接器生成 .exe
# x86_64-w64-mingw32-gcc 会自动处理影子空间初始化和库链接
x86_64-w64-mingw32-gcc win.obj -o win_test.exe

4.编译后在windows上测试

windows64位

1.windows64位代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
default rel             ; 使用相对寻址,Windows x64 的标准

section .data
hello db 'Hello world!', 0 ; C 风格字符串以 0 结尾

section .text
global main ; 如果使用 gcc 链接,入口通常是 main
extern puts ; 声明外部 C 函数
extern ExitProcess ; 声明外部 Windows API

main:
; --- 栈对齐与影子空间 ---
; 预留 32 (影子空间) + 8 (对齐) = 40 字节
sub rsp, 40

; --- 调用 puts(hello) ---
lea rcx, [hello] ; 第一个参数:字符串地址放入 rcx
call puts ; 调用 C 库函数输出并换行

; --- 调用 ExitProcess(0) ---
xor rcx, rcx ; 第一个参数:退出码 0
call ExitProcess ; 结束进程

; 实际上这里不会执行到,但保持对称
add rsp, 40
ret

2.编译命令

1
2
3
nasm -f win64 hello.asm -o hello.obj
gcc -o hello.exe -s hello.obj
./hello.exe

NASM里的ndisasm工具将二进制代码反汇编

1
ndisasm -b 32 function.bin

格式转换与反汇编工具

objcopy

使用objcopy工具可以将可执行文件转换为二进制文件,可以使用以下命令

1
objcopy -O binary program.exe program.bin

​ 目标文件转为二进制文件

1
2
3
4
5
6
7
8
9
10
11
12
#obj转化为bin
objcopy -O binary asm.obj asm.bin
#bin转化为hex
objcopy -I binary -O ihex asm.bin asm.hex
#hex文件转化为bin
objcopy -I ihex -O binary asm.hex asm.bin
#bin转换elf
objcopy -I binary -O elf32-i386 -B i386 asm.bin asm.elf


#linux查看二进制代码
hexdump -C hello.bin

objdump

objdump查看a.o的代码段

1
2
3
4
5
objdump -d a.o
objdump -d -j .text hello
#intel语法
objdump -M intel -d asm.exe > a.txt

gdb

gdb反汇编代码

1
2
3
4
5
6
7
8
9
#运行gdb
gdb asm.exe
#设置为intel语法
set disassembly-flavor intel
#运行程序
file asm.exe
#反汇编main函数
disassemble main

将gdb的输出信息存到文件的方法

1
2
3
4
set logging file a.txt
set logging on
disassemble main
set logging off

一些其他工具

od#

另一个十分常用的工具是 od。该工具提供 -x 参数用于输出十六进制的文件原始数据。

1
2
3
$ od -x file1
0000000 6577 636c 6d6f 0a65
0000010

同样,为了让输出更加易读,可使用 -c 参数输出文本。

1
2
3
4
$ od -xc file1
0000000 6577 636c 6d6f 0a65
w e l c o m e \n
0000010

xxd#

xxd 是一个稍特殊的工具,它还提供了一个 -r 选项,可将十六进制信息转换回原始文件,可用于编辑 Hex 内容。

1
2
$ xxd file1
0000000: 7765 6c63 6f6d 650a welcome.

假设我们有 file2 文件,内容如下:

1
2
$ cat file2
000000: 7765 6c63 6f6d 650a

那么我们可以使用 -r 选项来将其转换为原文件内容:

1
2
$ xxd -r file2
welcome

vs2019编译器

命令行使用ml命令调用ml.exe程序编译汇编程序

  1. 打开vs2019控制台x64

  2. 切换汇编程序目录

  3. 编译命令

1
2
3
4
5
6
7
8
#生成obj文件
ml64.exe /c hello.asm 
#C 标准库函数被移动到了 ucrt.lib
#某些传统的 C 函数定义在 legacy_stdio_definitions.lib 中
link.exe hello.obj /subsystem:console /defaultlib:msvcrt.lib /defaultlib:ucrt.lib /defaultlib:kernel32.lib /defaultlib:legacy_stdio_definitions.lib /entry:main
#运行
./hello.exe

ML 和 ML64 命令行参考

链接器选项

4.汇编代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
; 声明外部函数
extern puts : proc
extern ExitProcess : proc

.data
; MASM 使用 db 定义字节,以 0 结尾
hello db 'Hello world!', 0

.code
main proc
; --- 栈对齐与影子空间 ---
; Windows x64 调用约定要求:
; 1. 32 字节的影子空间 (Shadow Space)
; 2. 调用 call 指令前,栈指针 RSP 必须 16 字节对齐
; (加上函数返回地址的 8 字节,这里 sub 40 是正确的)
sub rsp, 40

; --- 调用 puts(hello) ---
; 第一个参数放入 RCX
lea rcx, hello
call puts

; --- 调用 ExitProcess(0) ---
; 第一个参数放入 RCX
xor rcx, rcx
call ExitProcess

; 正常情况下不会执行到这里
add rsp, 40
ret
main endp

end