[BUG] GCC 汇编标签地址被意外截断
GCC 汇编标签地址被意外截断
我把这个问题发布在了 Stack Overflow
https://stackoverflow.com/questions/79238937/gcc-assembly-label-address-unexpectedly-truncated-without-warning-in-16-bit-code
问题描述
使用 gcc 处理 16 位汇编程序时遇到了一个奇怪的 BUG
特定情况下,写入寄存器的标签地址超过寄存器位宽时,会被默认截断,没有任何警告与报错。
这是问题的最小样本,将位于 0x8000 的 label 加载进 16-bit 寄存器。
.text
.global _start
.code16
_start:
movw $label, %ax
.org 0x8000
label:
以下是编译参数,我使用了 -Ttext=0x8000 设置默认的地址偏移。
gcc -Ttext=0x8000 -Werror -m16 -nostdlib -nostartfiles -no-pie -o test.out test.S
此时,$label 应该等于 0x8000 + 0x8000 = 0x10000。这是一个超过 16-bit 范围的值,但 gcc 并没有报错,而是正常编译。
我使用 objdump 查看反汇编结果,发现 0x10000 直接被截断为了 0x0。
objdump -S test.out > test.asm
test.out: file format elf32-i386
Disassembly of section .text:
00008000 <_start>:
8000: b8 00 00 00 00 mov $0x0,%eax
...
稍后我会证明出现这个问题的特定条件范围。
为了说明这不是 gcc 的正常行为,接下来我会给出几个例子。
1. -Ttext >= 0x10000 || .org >= 0x10000
我将 -Ttext 参数设为 0x10000,且不使用 .org
_start:
movw $label, %ax
label:
gcc -Ttext=0x10000 -Werror -m16 -nostdlib -nostartfiles -no-pie -o test.out test.S
gcc 输出了错误信息(这是正常情况):
/tmp/ccFfC1x9.o: in function `_start':
(.text+0x1): relocation truncated to fit: R_386_16 against `.text'
collect2: error: ld returned 1 exit status
或者,使用 -Ttext=0x0 而 .org 0x10000
_start:
movw $label, %ax
.org 0x10000
label:
gcc -Ttext=0x0 -Werror -m16 -nostdlib -nostartfiles -no-pie -o test.out test.S
gcc 输出了错误信息(这也是正常情况):
test.S: Assembler messages:
test.S:6: Error: value of 00010000 too large for field of 2 bytes at 00000001
**2. .org < 0x8000 && -Ttext + .org >= 0x10000 **
这次,在 .org 小于 0x8000 的情况下,我们让 -Ttext 和 .org 的值相加大于 0x10000。
_start:
movw $label, %ax
.org 0x1000
label:
gcc -Ttext=0xF000 -Werror -m16 -nostdlib -nostartfiles -no-pie -o test.out test.S
这本质上和第一种情况是相同的,所以 gcc 也正常地报错了:
/tmp/cceqdykr.o: in function `_start':
(.text+0x1): relocation truncated to fit: R_386_16 against `.text'
collect2: error: ld returned 1 exit status
3. .org >= 0x8000 && -Ttext + .org >= 0x10000
上面几个都是正常的情况,但如果我们让 .org 处于 [0x8000, 0x10000) 的范围内,且 -Ttext 和 .org 的值相加大于 0x10000,事情就变得奇怪了起来。
_start:
movw $label, %ax
.org 0x8001
label:
gcc -Ttext=0x8000 -Werror -m16 -nostdlib -nostartfiles -no-pie -o test.out test.S
他应该报错的对吧?毕竟此时 $label = 0x8000 + 0x8001 = 0x10001 明显超出了 16-bit 的范围。但它却成功编译了:
test.out: file format elf32-i386
Disassembly of section .text:
00008000 <_start>:
8000: b8 01 00 00 00 mov $0x1,%eax
...
可以看出 0x10001 被截断为了 0x1 ,这真的很奇怪。
对此,我总结了如下规律
// 使用 start 和 offset 代指 -Ttext 和 .org 的值
// -Ttext = $start
// .org $offset
if start + offset > 0xFFFF
if start > 0xFFFF
// 报错
else if offset > 0xFFFF
// 报错
else if offset >= 0x8000
// 默认截断 start + offset 的值(为什么???)
else
// 报错
else
// 正常计算 start + offset
运行环境
gcc
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/14.2.1/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: /build/gcc/src/gcc/configure --enable-languages=ada,c,c++,d,fortran,go,lto,m2,objc,obj-c++,rust --enable-bootstrap --prefix=/usr --lir
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 14.2.1 20240910 (GCC)
ld
GNU ld (GNU Binutils) 2.43.0
系统
OS: Arch Linux on Windows 10 x86_64
Kernel: 5.15.167.4-microsoft-standard-WSL2
本文发布于2024年11月30日
最后修改于2024年11月30日

浙公网安备 33010602011771号