TL;DR
Statically linked ELF32 binaries may stop using the int 0x80 syscall after certain build or environment changes. This guide explains how to identify and fix this issue, ensuring your C library functions can make system calls correctly.
Understanding the Problem
When you statically link an ELF32 binary, all necessary code from libraries (like glibc) is copied directly into your executable. Traditionally, these binaries use the int 0x80 instruction to trigger system calls. However, some build configurations or environments can cause this mechanism to break down, often resulting in a crash when a syscall is attempted.
Identifying the Issue
- Symptoms: Your program crashes with a segmentation fault (segfault) or similar error when calling functions like
open(),read(),write(), etc. - Debugging: Use a debugger (like GDB) to step through the code and identify where the crash occurs. Pay close attention to any calls involving system services. The instruction pointer will likely be stuck in an unexpected location or attempting invalid memory access.
- Disassembly: Disassemble the relevant function using
objdumpor a similar tool. Look for the absence ofint 0x80instructions where you expect them.objdump -d your_program | grep int 0x80
Fixing the Problem
The most common cause is a mismatch between how the binary expects to make syscalls and the environment it’s running in. Here are several solutions:
1. Rebuild with Correct Syscall Support
- Check Compiler Flags: Ensure your compiler flags include support for 32-bit system calls. This is often handled automatically, but verify.
- For GCC, ensure you are compiling for the correct architecture (e.g.,
-m32).
- For GCC, ensure you are compiling for the correct architecture (e.g.,
- Linker Flags: Double-check your linker flags to make sure all necessary libraries are included and linked statically.
gcc -static -m32 your_program.c -o your_program - glibc Version: Ensure you’re using a compatible version of glibc for your target environment. Older versions might have issues with certain syscall mechanisms.
2. Use `syscall()` Wrapper (Recommended)
The syscall() function provides a more portable way to make system calls, especially in statically linked binaries. It’s available in glibc and avoids the direct use of int 0x80.
- Include Header: Include the necessary header file.
#include - Replace `int 0x80` Calls: Replace direct syscall calls with
syscall(). The arguments tosyscall()are the system call number, and then the system call’s parameters.For example, instead of using a wrapper around
int 0x80foropen(), use:#include #include int open(const char *pathname, int flags) { return syscall(__NR_open, pathname, flags); }Note:
__NR_openis the system call number foropen(). These numbers are architecture-specific and defined in/usr/include/asm/unistd_32.h(or similar). You’ll need to find the correct number for each syscall you use.
3. Check Environment
- Kernel Compatibility: Ensure your kernel supports the system calls your binary is trying to make. Very old kernels might lack certain features.
- Library Conflicts: Avoid conflicts with other libraries that might be interfering with syscall handling. This is less common but can occur in complex environments.
Further Debugging
If the problem persists, consider these steps:
- strace: Use
straceto monitor system calls made by your program. This can help identify which syscall is failing and with what arguments.strace ./your_program - ltrace: Use
ltraceto trace library calls, which might reveal issues within glibc itself.ltrace ./your_program