变量值正确仍触发Segfault?C+汇编程序崩溃求助
CPU标志位读取程序崩溃问题排查与修复
问题现象
程序调用addsub()函数后,gdb显示flags变量值正确,但执行打印语句读取该变量时程序崩溃;移除打印语句则程序能正常运行。
程序代码
main.c
#include <stdio.h> #include <stdlib.h> extern unsigned short addsub(int op1, int op2, char what, int *res, int *bit_count); void print_flags(unsigned short flags) { printf("Flags:\n"); printf("%s", "O D I T S Z A P C\n"); int bits_to_display = 12; // Significant bits only (0 included) for (int i = bits_to_display - 1; i >= 0; --i) { printf("%d ", (flags >> i) & 1); } printf("\n\n"); } void print_result(int op1, int op2, int result, unsigned short flags, char operation, int bit_count) { // Signed interpretation printf("Ergebnis und Operanden Signed:\n"); printf("%d %c %d = %d ", (signed char)op1, operation, (signed char)op2, (signed char)result); if ((flags & 0x0800) != 0) { // Overflow flag (OF) printf("(Ergebnis ist falsch!)\n"); } else { printf("(Ergebnis ist richtig!)\n"); } // Unsigned interpretation printf("\nErgebnis und Operanden Unsigned:\n"); printf("%u %c %u = %u ", (unsigned char)op1, operation, (unsigned char)op2, (unsigned char)result); if ((flags & 0x0001) != 0) { // Carry flag (CF) printf("(Ergebnis ist falsch!)\n"); } else { printf("(Ergebnis ist richtig!)\n"); } // Print POPCNT result printf("\nDie Anzahl der gesetzten Bits im Ergebnis beträgt: %d\n", bit_count); } int main(int argc, char *argv[]) { if (argc != 4) { fprintf(stderr, "Usage: %s <operand1> <+|-> <operand2>\n", argv[0]); return 1; } int op1 = atoi(argv[1]); int op2 = atoi(argv[3]); char op = argv[2][0]; if (op != '+' && op != '-') { fprintf(stderr, "Error: Invalid operator. Use '+' or '-'.\n"); return 1; } int res = 0; int bit_count = 0; unsigned short flags = addsub(op1, op2, op, &res, &bit_count); if (bit_count == -1) { printf("Diese CPU unterstützt den Befehl CPUID nicht!\n"); return 1; } else if (bit_count == -2) { printf("Diese CPU unterstützt den Befehl POPCNT nicht!\n"); return 1; } print_flags(flags); print_result(op1, op2, res, flags, op, bit_count); return 0; }
汇编模块
section .text global addsub ; CPUID function genutzt um cpu informationen auszulesen ; EAX = 1 provides feature information in ECX ; Bit 23 of ECX = 1 indicates POPCNT support addsub: push ebp mov ebp, esp ; Load function arguments mov eax, [ebp+8] ; Load op1 mov ebx, [ebp+12] ; Load op2 mov ecx, [ebp+16] ; Load operation ('+' or '-') mov edx, [ebp+20] ; Load address of res mov esi, [ebp+24] ; Load address of bit_count ; Save registers push eax push ebx push ecx push edx push esi ; CPUID for POPCNT support check pushfd pop eax mov ecx, eax xor eax, 0x00200000 ; Toggle ID bit in EFLAGS push eax ; Save modified flags popfd ; Load modified flags pushfd ; Push modified flags to stack pop eax ; Pop flags back into EAX xor eax, ecx ; Check if ID bit was toggled jz no_cpuid ; CPUID not supported ; CPUID supported, check POPCNT support (bit 23) mov eax, 1 ; Set EAX to 1 (feature information) cpuid test ecx, 1 << 23 jz no_popcnt ; POPCNT not supported ; Restore registers pop esi pop edx pop ecx pop ebx pop eax cmp cl, '+' je add cmp cl, '-' je sub ; Invalid operation xor eax, eax leave ret add: add al, bl mov [edx], eax ; Store result pushfd pop eax jmp count_bits sub: sub al, bl mov [edx], eax ; Store result pushfd pop eax jmp count_bits count_bits: push ebx push esi mov ebx, [edx] and ebx, 0xFF ; Hardcoded 8-bit limit popcnt ecx, ebx ; Count the number of set bits in result mov [esi], ecx ; Store bit count in variable pop ebx pop esi jmp fin no_cpuid: mov dword [esi], -1 ; CPUID not supported flag for C program jmp fin no_popcnt: mov dword [esi], -2 ; POPCNT not supported flag for C program jmp fin fin: leave ; Restore stack frame (includes pop ebp) ret
问题根源
- 栈平衡被破坏:在
no_cpuid、no_popcnt以及无效操作分支中,没有恢复函数开头push到栈中的寄存器(eax、ebx、ecx、edx、esi),直接跳转或返回,导致栈指针错位。函数返回后,调用者的栈帧被破坏,后续访问变量时会访问错误的内存地址,引发崩溃。 - 返回值位数不匹配:C函数声明返回
unsigned short(16位),但汇编中直接返回32位的EFLAGS值,高16位可能包含无效数据,虽然gdb显示低16位正确,但类型不匹配可能触发未定义行为。 - 栈操作顺序错误:
count_bits中push ebx和push esi后,pop顺序错误(应该先pop esi再pop ebx),进一步加剧栈不平衡。
修复步骤
1. 统一恢复所有分支的栈寄存器
修改no_cpuid、no_popcnt和无效操作分支,先弹出保存的寄存器再处理:
no_cpuid: pop esi pop edx pop ecx pop ebx pop eax mov dword [esi], -1 jmp fin no_popcnt: pop esi pop edx pop ecx pop ebx pop eax mov dword [esi], -2 jmp fin ; Invalid operation pop esi pop edx pop ecx pop ebx pop eax xor eax, eax leave ret
2. 修正返回值位数
在add和sub分支中,获取EFLAGS后只保留低16位:
add: add al, bl mov [edx], eax pushfd pop eax and eax, 0xFFFF ; 截断为16位,匹配unsigned short类型 jmp count_bits sub: sub al, bl mov [edx], eax pushfd pop eax and eax, 0xFFFF ; 截断为16位,匹配unsigned short类型 jmp count_bits
3. 修正count_bits的栈操作顺序
栈是后进先出,调整pop顺序:
count_bits: push ebx push esi mov ebx, [edx] and ebx, 0xFF popcnt ecx, ebx mov [esi], ecx pop esi ; 先弹出后压入的esi pop ebx ; 再弹出ebx jmp fin
修复原理
- 栈平衡恢复后,调用者的栈帧不会被破坏,后续变量访问能正确指向内存地址。
- 返回值截断为16位后,与C函数的返回类型完全匹配,避免类型不匹配导致的未定义行为。
- 修正栈操作顺序后,
count_bits内部的栈也保持平衡,不会影响整体栈状态。
内容的提问来源于stack exchange,提问作者burneraccount
相关产品推荐
相关产品推荐

