[toc]
GCC编译器的基本使用 gcc的编译过程
预处理。
编译。用编译器将预处理代码转换为汇编代码
汇编。用汇编器将汇编代码转换为机器码,产生的文件叫做目标文件
1 2 gcc -c main.s -o main.o
链接。链接过程使用链接器将该目标文件与其他目标文件、库文件、启动文件等链接起来生成可执行文件
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 sudo apt-get install gcc-multilib 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 ld -o function.bin -Ttext 0x0 --oformat binary 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 objdump -d -m i8086 code16.elf objdump -D -m i8086 -b binary code16.com
gcc内连汇编
1 2 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
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 格式的源代码:
相关链接:
汇编语言–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 as test.s -o test.o gcc test.o -o test
NASM汇编器 NASM 使用的是 Intel 的汇编格式:
使用以下命令将汇编代码编译成可执行文件: linux64位 在 Linux 64 位环境下,代码需要进行两个核心调整:
寄存器传参规则 :从 32 位的 eax, ebx, ecx, edx 变为 64 位的 rax, rdi, rsi, rdx。
中断方式 :从 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 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 2 nasm hello.asm -f bin -o hello.bin
编写代码
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(启动魔数)
使用 QEMU 虚拟机
1 qemu-system-x86_64 -drive format=raw,file=boot.bin
方法 B:写入 U 盘并在真机启动
插入 空U 盘,确定其设备名(如 /dev/sdb)。
使用 dd 命令写入:
1 sudo dd if =boot.bin of=/dev/sdb bs=512 count=1
重启电脑,在 BIOS 中选择从该 U 盘启动(需开启 Legacy Support/CSM 模式,不支持纯 UEFI)。
Linux aarm64上交叉编译linux64位程序 1.编译命令 1 2 3 4 5 6 7 8 9 sudo apt update sudo apt install binutils-x86-64-linux-gnu nasm -f elf64 hello.asm -o hello.o 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 qemu-x86_64 ./hello qemu-x86_64 -L /usr/x86_64-linux-gnu ./hello
3.查看文件格式
如果输出包含 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 nasm -f win64 win.asm -o win.obj 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 objcopy -O binary asm.obj asm.bin objcopy -I binary -O ihex asm.bin asm.hex objcopy -I ihex -O binary asm.hex asm.bin objcopy -I binary -O elf32-i386 -B i386 asm.bin asm.elf hexdump -C hello.bin
objdump objdump查看a.o的代码段
1 2 3 4 5 objdump -d a.o objdump -d -j .text hello objdump -M intel -d asm.exe > a.txt
gdb gdb反汇编代码
1 2 3 4 5 6 7 8 9 gdb asm.exe set disassembly-flavor intelfile asm.exe disassemble main
将gdb的输出信息存到文件的方法
1 2 3 4 set logging file a.txtset logging ondisassemble main set logging off
一些其他工具 另一个十分常用的工具是 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 选项来将其转换为原文件内容:
vs2019编译器 命令行使用ml命令调用ml.exe程序编译汇编程序
打开vs2019控制台x64
切换汇编程序目录
编译命令
1 2 3 4 5 6 7 8 ml64.exe /c hello.asm 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