交叉编译
本文简单说明在 ARM M1 的 Mac 电脑上,使用 GCC 工具交叉编译文件到 i386 或 x86-64 的目标机器上。
Cross compile source C file to i386 (or x86-64) targeted machine on ARM M1 Mac by GCC.
最终目的是来分析一下 80386 指令集的实现方式(即 copy/v86 的源码),所以需要 16 位的机器码来看,最终选取 docker i386/gcc 来实现交叉编译。
说明
因为目前很多机器都是 64 位,且有些环境是 x86 有些是 arm,会涉及到交叉编译,所以,为了统一构建环境,使用 docker 来构建,毕竟踩了几天的坑,只有此方式比较方便。
机器上的交叉编译
比如,在 mac 上,使用 x86_64-elf-gcc
来编译,能编译成汇编,但 link 会出问题
在 x86 的 intel mac 上,也是一样的。
# 安装交叉编译器
$ brew install x86_64-elf-gcc
# 简单的源码
$ cat add.c
int main() {
int a = 1;
int b = 2;
int c = a + b;
return c;
}%
# 编译为汇编
$ x86_64-elf-gcc -m32 ./add.c -S
# 查看汇编,是能正常编译出来的
$ cat add.s
.file "add.c"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl $1, -4(%ebp)
movl $2, -8(%ebp)
movl -4(%ebp), %edx
movl -8(%ebp), %eax
addl %edx, %eax
movl %eax, -12(%ebp)
movl -12(%ebp), %eax
leave
ret
.size main, .-main
.ident "GCC: (GNU) 12.2.0"
# 无法正常 link
# 此类、或类似的问题,搞了很久就搞不定,所以,用 docker 来搞了
$ x86_64-elf-gcc -m32 ./add.c
/opt/homebrew/opt/x86_64-elf-binutils/bin/x86_64-elf-ld: cannot find crt0.o: No such file or directory
/opt/homebrew/opt/x86_64-elf-binutils/bin/x86_64-elf-ld: cannot find -lc: No such file or directory
collect2: error: ld returned 1 exit status
# 正常构建不指定目标代码也不行
$ x86_64-elf-gcc ./add.c
/opt/homebrew/opt/x86_64-elf-binutils/bin/x86_64-elf-ld: cannot find crt0.o: No such file or directory
/opt/homebrew/opt/x86_64-elf-binutils/bin/x86_64-elf-ld: cannot find -lc: No such file or directory
collect2: error: ld returned 1 exit status
# 查了较多资料,不指定标准库,以及重新指定 entry,是能正常编译的
$ x86_64-elf-gcc -nostdlib --entry main ./add.c
$ objdump -S a.out
a.out: file format elf64-x86-64
Disassembly of section .text:
0000000000400078 <main>:
400078: 55 pushq %rbp
400079: 48 89 e5 movq %rsp, %rbp
40007c: c7 45 fc 01 00 00 00 movl $1, -4(%rbp)
400083: c7 45 f8 02 00 00 00 movl $2, -8(%rbp)
40008a: 8b 55 fc movl -4(%rbp), %edx
40008d: 8b 45 f8 movl -8(%rbp), %eax
400090: 01 d0 addl %edx, %eax
400092: 89 45 f4 movl %eax, -12(%rbp)
400095: 8b 45 f4 movl -12(%rbp), %eax
400098: 5d popq %rbp
400099: c3 retq
Steps
docker pull i386/gcc
来拉取 i386 的 gcc 版本- 参考 Docker: Copying files from Docker container to host 来将需要构建的文件放入 docker 容器中。
$ docker cp containerId:/source/hello.c ./hello.c
$ docker cp ./helloc containerId:/dest
docker run -it i386/gcc
运行容器docker run -it --name=gcc -v "$PWD:/home" i386/gcc
或将当前目录直接挂接上去docker exec -it <ID> bash
在当前容器中新开 shell
# echo "int main() { int a = 1; int b = 2; int c = a + b; return c; }" > hello.c
添加测试代码gcc -mtune=i386 -m16 hello.c
- 用于交叉编译 i386 的 16 位程序,主要是为了和 Opcode Table 对应起来,32 位有
movl
等指令,无法较好的对应。
- 用于交叉编译 i386 的 16 位程序,主要是为了和 Opcode Table 对应起来,32 位有
- 参考 How to disassemble a binary executable in Linux to get the assembly code?, How to disassemble one single function using objdump? 将二进制文件反编译为汇编代码,并仅选取
main
函数objdump -S a.out | awk -F"\n" -v RS="\n\n" '$1 ~ /main/'
Code Example
16 位机器汇编样例
# 源码
$ cat hello.c
int main() { int a = 1; int b = 2; int c = a + b; return c; }
# 编译
$ gcc -mtune=i386 -m16 hello.c
# 查看二进制与汇编
$ objdump -S a.out | awk -F"\n" -v RS="\n\n" '$1 ~ /main/'
080482a0 <__libc_start_main@plt>:
80482a0: ff 25 64 96 04 08 jmp *0x8049664
80482a6: 68 08 00 00 00 push $0x8
80482ab: e9 d0 ff ff ff jmp 8048280 <_init+0x2c>
080483ab <main>:
80483ab: 66 55 push %bp
80483ad: 66 89 e5 mov %sp,%bp
80483b0: 66 83 ec 10 sub $0x10,%sp
80483b4: 67 66 c7 45 fc 01 00 movw $0x1,-0x4(%di)
80483bb: 00 00 add %al,(%eax)
80483bd: 67 66 c7 45 f8 02 00 movw $0x2,-0x8(%di)
80483c4: 00 00 add %al,(%eax)
80483c6: 67 66 8b 55 fc mov -0x4(%di),%dx
80483cb: 67 66 8b 45 f8 mov -0x8(%di),%ax
80483d0: 66 01 d0 add %dx,%ax
80483d3: 67 66 89 45 f4 mov %ax,-0xc(%di)
80483d8: 67 66 8b 45 f4 mov -0xc(%di),%ax
80483dd: 66 c9 leavew
80483df: 66 c3 retw
80483e1: 66 90 xchg %ax,%ax
80483e3: 66 90 xchg %ax,%ax
80483e5: 66 90 xchg %ax,%ax
80483e7: 66 90 xchg %ax,%ax
80483e9: 66 90 xchg %ax,%ax
80483eb: 66 90 xchg %ax,%ax
80483ed: 66 90 xchg %ax,%ax
80483ef: 90 nop
32 位机器汇编样例
# 源码
$ cat hello.c
int main() { int a = 1; int b = 2; int c = a + b; return c; }
# 编译
$ gcc -m32 hello.c
# 查看二进制与汇编
$ objdump -S a.out | awk -F"\n" -v RS="\n\n" '$1 ~ /main/'
080482a0 <__libc_start_main@plt>:
80482a0: ff 25 44 96 04 08 jmp *0x8049644
80482a6: 68 08 00 00 00 push $0x8
80482ab: e9 d0 ff ff ff jmp 8048280 <_init+0x2c>
080483ab <main>:
80483ab: 55 push %ebp
80483ac: 89 e5 mov %esp,%ebp
80483ae: 83 ec 10 sub $0x10,%esp
80483b1: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%ebp)
80483b8: c7 45 f8 02 00 00 00 movl $0x2,-0x8(%ebp)
80483bf: 8b 55 fc mov -0x4(%ebp),%edx
80483c2: 8b 45 f8 mov -0x8(%ebp),%eax
80483c5: 01 d0 add %edx,%eax
80483c7: 89 45 f4 mov %eax,-0xc(%ebp)
80483ca: 8b 45 f4 mov -0xc(%ebp),%eax
80483cd: c9 leave
80483ce: c3 ret
80483cf: 90 nop