Powernews Tuesday, 18 August 2026 at 21:00 CEST
UNIX COMMAND OF THE DAY

Objdump: Disassembling Executable Machine Code, Inspecting Section Relocations, and Auditing Binary Security in Production

The sharp buzz of an on-call phone cutting through the silence of a bedroom at two in the morning is a sound no engineer forgets. You stumble to your desk in the dark, blinking against the harsh glare of a laptop screen, only to find the incident channel overflowing with frantic messages: checkout services are failing across the board, customer orders are evaporating into thin air, and revenue is plummeting by the second. Your team is clustered on an emergency bridge call, exchanging theories in mounting disbelief, but nobody can say why the servers suddenly dropped dead.
Key Takeaway
Essential takeaway summary for Objdump: Disassembling Executable Machine Code, Inspecting Section Relocations, and Auditing Binary Security in Production.

The usual diagnostics offer no comfort. The production servers run inside ultra-minimalist containers stripped of every convenienceβ€”there is no interactive debugger installed, no memory profiler available, and restrictive disk quotas ensured that when the process collapsed, it left behind no crash dump file to inspect. The only clue is a single cryptic line buried in the operating system's kernel log: a sequence of hexadecimal numbers marking the exact memory address where execution ceased.

When standard monitoring dashboards and log aggregators go blind, you must stop guessing and interrogate the compiled application directly. Rather than treating the software as an impenetrable black box, you can turn to objdump, an indispensable diagnostic workhorse in the GNU Binutils Suite that translates raw binary files back into readable instructions.

To immediately inspect what an application is telling your computer's processor to do, you can run a single command from your terminal:

objdump -d -M intel -C --no-show-raw-insn /usr/bin/sys_collector | head -n 25

This command translates the cryptic machine code of any Linux executable into clear, structured assembly language using familiar Intel syntax, demangling complex function names so you can read them at a glance. In seconds, the opaque binary reveals its inner anatomy, displaying every function, jump, and memory operation exactly as the silicon will execute them. It is the closest thing systems engineering has to an X-ray machine for running software.


What It Does in Plain English

Every program you runβ€”whether written in C, C++, Rust, or Goβ€”ultimately gets translated by a compiler into machine code: a dense sequence of raw bytes that tell your computer's central processing unit (CPU) exactly how to shuffle data between memory and registers. In Linux environments, these instructions and their supporting metadata are packaged into files using the Executable and Linkable Format (ELF).

At its heart, objdump is a binary inspection engine and disassembler. It opens compiled executables, shared libraries (.so files), and object files (.o files), reads the raw hexadecimal bytes stored within their code sections, and translates them back into human-readable assembly instructions. Crucially, objdump does this through static analysisβ€”meaning it never actually executes the target program. It allows you to safely dissect an unknown, crashed, or potentially malicious program without having to run it or risk destabilising your system.

sequenceDiagram autonumber actor Admin as Sysadmin / Forensic Analyst participant Kernel as Linux Kernel / dmesg participant Objdump as objdump Utility participant Binary as Compiled ELF Binary (.text / .rodata / .got) Kernel->>Admin: Emits crash offset or unhandled fault address Admin->>Objdump: Executes targeted disassembly with -d -M intel Objdump->>Binary: Reads section headers, symbol tables & machine bytecode Binary-->>Objdump: Streams raw opcodes & relocation entries Objdump-->>Admin: Renders decoded assembly, demangled symbols & text Admin->>Admin: Identifies root cause (e.g. null pointer, missing symbol, unvectorized loop)

Core Flags and Bytecode Anatomy

Navigating the interior of a compiled binary requires precision. The objdump command provides several foundational switches tailored for low-level systems triage:

  • -d (--disassemble): Disassembles executable machine instructions contained exclusively within active code sections (such as .text).
  • -D (--disassemble-all): Disassembles every section in the object file, including non-executable data sections.
  • -M intel (--disassembler-options=intel): Formats x86/x86-64 assembly using Intel syntax (<instruction> <destination>, <source>) rather than the default AT&T syntax.
  • -C (--demangle): Demangles low-level symbol names generated by C++ or Rust compilers into human-readable function signatures.
  • -t (--syms): Dumps the static symbol table, exposing internal function names, global variables, and compilation units.
  • -T (--dynamic-syms): Displays dynamic symbol table entries required during runtime linking by the dynamic loader (ld.so).
  • -r / -R (--reloc / --dynamic-reloc): Dumps static and dynamic relocation entries, exposing address patch points managed by the linker.
  • -S (--source): Interleaves original source code lines with their corresponding assembly instructions when debug information is present.
  • -h (--section-headers): Prints concise summary headers for all sections present in the object file.
  • -j <section> (--section=<section>): Restricts analysis or disassembly to a specific named section (e.g., -j .text, -j .rodata).
  • -s (--full-contents): Displays the full raw hexadecimal and ASCII contents of requested sections.

Deconstructing the Bytecode Stream

When conducting forensic analysis, preserving raw instruction bytes alongside the assembly text is essential for checking processor alignment and instruction widths:

objdump -d -M intel -C /usr/local/bin/crypto_worker
/usr/local/bin/crypto_worker:     file format elf64-x86-64

Disassembly of section .text:

00000000000011a0 <crypto_init_context>:
    11a0:   f3 0f 1e fa             endbr64 
    11a4:   55                      push   rbp
    11a5:   48 89 e5                mov    rbp,rsp
    11a8:   48 83 ec 20             sub    rsp,0x20
    11ac:   48 89 7d e8             mov    QWORD PTR [rbp-0x18],rdi
    11b0:   48 8b 45 e8             mov    rax,QWORD PTR [rbp-0x18]
    11b4:   48 8b 00                mov    rax,QWORD PTR [rax]
    11b7:   48 85 c0                test   rax,rax
    11ba:   74 0b                   je     11c7 <crypto_init_context+0x27>
    11bc:   b8 00 00 00 00          mov    eax,0x0
    11c1:   48 83 c4 20             add    rsp,0x20
    11c5:   5d                      pop    rbp
    11c6:   c3                      ret    
    11c7:   e8 84 fe ff ff          call   1050 <abort@plt>

Anatomical Breakdown of Disassembly Columns

Every line in the disassembly output represents a direct command given to the underlying processor:

  1. Virtual Memory Address (11a0:, 11ac:): The memory offset where the instruction resides within the program. In Position-Independent Executables (PIE), this displays the offset from the base address where the operating system loads the file.
  2. Raw Machine Bytecode (f3 0f 1e fa, 48 89 7d e8): The actual hexadecimal numbers read by the CPU. Prefix bytes like 48 (the REX prefix) inform a 64-bit processor to work with full 64-bit wide registers.
  3. Instruction Mnemonic (endbr64, mov, test, je, call): The human-readable name of the action the processor must perform (e.g. move data, compare values, or jump to another address).
  4. Operands (QWORD PTR [rbp-0x18], rdi): The targets and sources of the operation, such as registers, fixed numbers, or memory locations qualified by pointer size (QWORD for 64-bit, DWORD for 32-bit, WORD for 16-bit, and BYTE for 8-bit).

5 Real-World Production Use Cases


Use Case 1: Post-Mortem Crash Triage from Kernel dmesg Offsets

The Scenario

An ultra-low latency C++ trading daemon crashes in production with a segmentation fault during a period of heavy market volatility. Core dump generation was disabled to save disk space, but /var/log/messages and the dmesg system log captured a single diagnostic trace from the Linux kernel:

traps: market_feeder[28419] general protection fault ip:55b8e90c25a1 sp:7ffd9a1c8900 error:0 in market_feeder[55b8e90c0000+8000]

The log reveals that the crash occurred at virtual memory address 0x55b8e90c25a1, while the binary's code segment was loaded starting at base address 0x55b8e90c0000. By subtracting the base address from the instruction pointer:

$$\text{Crash Offset} = \text{0x55b8e90c25a1} - \text{0x55b8e90c0000} = \text{0x25a1}$$

The engineer needs to inspect the exact instruction located at offset 0x25a1 inside the executable file.

The Command

Using the --start-address and --stop-address flags, you can target the immediate neighbourhood of the crash without scrolling through thousands of lines of irrelevant code:

objdump -d -M intel -C --start-address=0x2580 --stop-address=0x25c0 /usr/local/bin/market_feeder

Terminal Output

/usr/local/bin/market_feeder:     file format elf64-x86-64

Disassembly of section .text:

0000000000002580 <OrderBook::process_depth(OrderUpdate const*)>:
    2580:   f3 0f 1e fa             endbr64 
    2584:   48 85 f6                test   rsi,rsi
    2587:   74 25                   je     25ae <OrderBook::process_depth(OrderUpdate const*)+0x2e>
    2589:   48 83 ec 10             sub    rsp,0x10
    258d:   48 89 7c 24 08          mov    QWORD PTR [rsp+0x8],rdi
    2592:   48 89 74 24 00          mov    QWORD PTR [rsp],rsi
    2597:   48 8b 44 24 00          mov    rax,QWORD PTR [rsp]
    259c:   48 8b 40 18             mov    rax,QWORD PTR [rax+0x18]
    25a1:   48 8b 10                mov    rdx,QWORD PTR [rax]
    25a4:   48 89 54 24 08          mov    QWORD PTR [rsp+0x8],rdx
    25a9:   48 83 c4 10             add    rsp,0x10
    25ad:   c3                      ret    
    25ae:   b8 ea ff ff ff          mov    eax,0xffffffea
    25b3:   c3                      ret    

Line-by-Line Disassembly Analysis

  • 2580: endbr64 verifies branch integrity for modern processor security (Intel CET).
  • 2584 - 2587: The function tests whether the incoming pointer (rsi, representing OrderUpdate const*) is null via test rsi, rsi. If it is null, it jumps to 25ae to exit safely with an error code.
  • 259c: mov rax, QWORD PTR [rax+0x18] loads an internal nested pointer offset (+24 bytes) from the OrderUpdate data structure into the rax register.
  • 25a1: The Crash Site (0x25a1). mov rdx, QWORD PTR [rax] attempts to read memory from the address stored inside rax. Because that nested struct member was uninitialized or pointed to invalid memory (0x0), attempting to read it triggered an immediate protection fault.

What the Admin Does Next

  1. Alert the development team that while the outer OrderUpdate object was checked for null, an inner structure pointer located at offset +24 bytes was dereferenced without validation.
  2. Apply a patch in the C++ codebase to validate OrderUpdate->sub_entry prior to access, recompile, and deploy the fix to staging.

Use Case 2: Diagnosing Dynamic Symbol Resolution & PIE Relocation Failures

The Scenario

During a rolling update across a server cluster, an analytics service crashes immediately on startup with a fatal dynamic linker error:

/opt/analytics/bin/engine: symbol lookup error: /opt/analytics/lib/libshim.so: undefined symbol: compute_fast_hash

The developers state that the library was built against the newest headers. As the administrator, you must inspect the dynamic symbol tables (-T) and relocations (-R) across the shared libraries to determine whether the function is truly missing, has suffered an ABI name-mangling mismatch, or failed to bind properly in the Global Offset Table.

The Command

First, check the dynamic relocation entries of the failing library to see what symbol format it expects:

objdump -R /opt/analytics/lib/libshim.so | grep compute_fast_hash

Next, inspect the exported dynamic symbol table of the upstream dependency /usr/local/lib/libcore_utils.so:

objdump -T /usr/local/lib/libcore_utils.so | grep compute_fast_hash

Terminal Output

From libshim.so:

0000000000003fd8 R_X86_64_JUMP_SLOT  compute_fast_hash

From libcore_utils.so:

0000000000014b20 g    DF .text  000000000000004a  Base        _Z17compute_fast_hashPKcm

Line-by-Line Disassembly Analysis

  • libshim.so output: The relocation entry R_X86_64_JUMP_SLOT compute_fast_hash indicates that libshim.so expects a standard, unmangled C function named compute_fast_hash at runtime.
  • libcore_utils.so output: The upstream library does not export compute_fast_hash. Instead, it exports the mangled string _Z17compute_fast_hashPKcm.
  • Passing that string to the symbol demangler (c++filt _Z17compute_fast_hashPKcm) produces compute_fast_hash(char const*, unsigned long). The upstream library was compiled with a C++ compiler without wrapping the header declaration in an extern "C" block, changing its exported symbol name and breaking binary compatibility.

What the Admin Does Next

  1. Roll back libcore_utils.so to the previous stable release on the cluster to restore production traffic immediately.
  2. Notify the software team that an ABI breakage occurred so they can wrap the function declaration in extern "C" and release an updated build.

Use Case 3: Auditing Compiler Optimization, Loop Unrolling, and SIMD Vectorization

The Scenario

Following an automated toolchain upgrade, a high-performance numerical computation library exhibits an unexpected 400% slowdown on AVX-512 capable hardware. You need to inspect the compiled output to verify whether the compiler successfully vectorized the core numerical calculation or reverted to slow, scalar single-instruction execution.

The Command

Disassemble the target function, interleaving original source code lines (-S) with Intel syntax instructions (-M intel) on the library built with debug symbols:

objdump -S -d -M intel -C --no-show-raw-insn /usr/local/lib/libmatrix_simd.so | sed -n '/<Matrix::vector_multiply/,/^[0-9a-f]/p'

Terminal Output

0000000000001420 <Matrix::vector_multiply(float const*, float const*, float*, unsigned long)>:
// Source line: void Matrix::vector_multiply(const float* a, const float* b, float* c, size_t n) {
    1420:   f3 0f 1e fa             endbr64 
    1424:   48 85 c9                test   rcx,rcx
    1427:   74 37                   je     1460 <Matrix::vector_multiply+0x40>
    1429:   48 89 c8                mov    rax,rcx
    142c:   31 d2                   xor    edx,edx
// Source line: for (size_t i = 0; i < n; ++i) { c[i] = a[i] * b[i]; }
    142e:   66 90                   xchg   ax,ax
    1430:   f3 0f 10 04 97          movss  xmm0,DWORD PTR [rdi+rdx*4]
    1435:   f3 0f 59 04 96          mulss  xmm0,DWORD PTR [rsi+rdx*4]
    143a:   f3 0f 11 04 92          movss  DWORD PTR [rdx+rdx*4],xmm0
    143f:   48 83 c2 01             add    rdx,0x1
    1443:   48 39 c2                cmp    rdx,rax
    1446:   75 e8                   jne    1430 <Matrix::vector_multiply+0x10>
    1448:   c3                      ret    
    1460:   c3                      ret    

Line-by-Line Disassembly Analysis

  • 1430: movss xmm0, DWORD PTR [rdi+rdx*4] uses a scalar instruction (movss = Move Scalar Single-Precision Floating-Point) rather than a wide vector instruction (vmovups).
  • 1435: mulss xmm0, DWORD PTR [rsi+rdx*4] performs a single multiplication per clock cycle. The CPU processes a single 32-bit float at a time instead of processing sixteen 32-bit floats simultaneously across a 512-bit register.
  • 143f - 1446: The generated loop increments rdx by 1 (add rdx, 0x1) on each cycle with no unrolling.
  • Root Cause: The compiler failed to vectorize because the function arguments float* a, float* b, and float* c were not marked with the __restrict__ keyword. The compiler conservatively assumed that writing to array c might overwrite data in a or b, disabling SIMD optimization.

What the Admin Does Next

  1. Inform the engineering team to annotate the pointer signatures with __restrict__ to guarantee memory regions do not overlap.
  2. Update the build flags to enforce -O3 -mavx512f -ftree-vectorize, then re-inspect with objdump to verify that vmovups and vmulps instructions are generated.

Use Case 4: Auditing Embedded Strings and Raw Metadata in Untrusted Binaries

The Scenario

During a security audit, an unknown binary named kworker_update is found running in /tmp on a staging server. Company policy strictly prohibits executing suspicious software, eliminating dynamic analysis tools like strace. You must inspect embedded URLs, configuration parameters, and authentication tokens directly from the binary file without running it.

The Command

Use objdump to inspect the section headers and dump the complete contents of the .rodata (read-only data) section in combined hexadecimal and ASCII format:

objdump -s -j .rodata /tmp/kworker_update

Terminal Output

/tmp/kworker_update:     file format elf64-x86-64

Contents of section .rodata:
 2000 01000200 68747470 3a2f2f31 39322e31  ....http://192.1
 2010 36382e31 2e323530 3a383038 302f6332  68.1.250:8080/c2
 2020 5f626561 636f6e00 504f5354 202f6170  _beacon.POST /ap
 2030 692f7631 2f657866 696c7472 61746500  i/v1/exfiltrate.
 2040 41555448 3a204265 61726572 20396638  AUTH: Bearer 9f8
 2050 33616362 65373132 30346166 61386200  3acbe71204afa8b.

Line-by-Line Disassembly Analysis

  • 2000 - 2027: The raw hexadecimal bytes decode directly to http://192.168.1.250:8080/c2_beacon, exposing the remote command-and-control endpoint.
  • 2028 - 203f: Decodes to POST /api/v1/exfiltrate, revealing the internal path used to siphon data.
  • 2040 - 205f: Extracts an embedded API authentication token: AUTH: Bearer 9f83acbe71204afa8b.

What the Admin Does Next

  1. Add an immediate firewall block rule for IP 192.168.1.250:8080 across all perimeter firewalls.
  2. Search central authentication logs for the extracted bearer token 9f83acbe71204afa8b to determine if other infrastructure components were compromised.

Use Case 5: Auditing Binary Hardening (Intel CET, Stack Canaries, and Full RELRO)

The Scenario

Before deploying an internet-facing reverse proxy (edge_proxy) into production, your security baseline requires verification that the binary has been compiled with essential modern exploit protections: 1. Control-Flow Enforcement Technology (CET / IBT): Ensuring indirect branch instructions (endbr64) protect function entry points against jump-oriented programming attacks. 2. Stack Smashing Protection (SSP): Ensuring stack canaries (__stack_chk_fail) protect memory buffers against buffer overflow exploits. 3. Full RELRO (Relocation Read-Only): Ensuring the Global Offset Table (.got) is marked read-only to prevent pointer overwrite attacks.

The Command

Perform a compound audit using objdump to verify instruction-level guards and section access permissions:

objdump -d -M intel -j .text /usr/sbin/edge_proxy | grep -E "(endbr64|__stack_chk_fail)"

Then check the access permissions of the Global Offset Table:

objdump -h -j .got -j .got.plt /usr/sbin/edge_proxy

Terminal Output

From Disassembly:

  10b0: f3 0f 1e fa             endbr64 
  10c4: e8 a7 ff ff ff          call   1070 <__stack_chk_fail@plt>
  1120: f3 0f 1e fa             endbr64 
  1152: e8 19 ff ff ff          call   1070 <__stack_chk_fail@plt>

From Section Headers:

/usr/sbin/edge_proxy:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
 21 .got          000001f8  0000000000003e08  0000000000003e08  00002e08  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA

Line-by-Line Disassembly Analysis

  • endbr64 (Opcode f3 0f 1e fa): Confirms Intel CET Indirect Branch Tracking is active. Any unauthorised jump into the middle of a function will trigger an immediate hardware protection fault.
  • call 1070 <__stack_chk_fail@plt>: Confirms stack canaries are active. If an attacker attempts to overflow a local buffer on the stack, the canary check fails and safely terminates the process.
  • .got flags (READONLY): Combined with the absence of a separate writable .got.plt section, the READONLY attribute confirms that Full RELRO was enforced via -Wl,-z,relro,-z,now. The dynamic linker resolves all addresses at startup and locks the lookup table as read-only.

What the Admin Does Next

  1. Sign off on the binary hardening audit, approving the service for production deployment.

What Can Go Wrong: Traps, Illusions, and Failure Modes

1. The AT&T vs. Intel Syntax Misinterpretation Trap

By default on x86 architectures, objdump displays assembly in AT&T syntax. In AT&T syntax, the source operand comes before the destination operand (mov %rax, %rbx copies data from register RAX to register RBX). In Intel syntax (-M intel), this order is reversed: destination precedes source (mov rbx, rax). Confusing these two formats during an outage can lead an engineer to mistake where data was being written versus where it was being read, causing hours of wasted debugging.

Mitigation: Standardise your workflows and internal documentation around -M intel, or configure a shell alias such as alias disasm='objdump -d -M intel -C'.

2. The Data-as-Code Disassembly Illusion (-D vs -d)

Running objdump -D tells the tool to disassemble every section of the file, including non-executable data regions like .data and .rodata. Because arbitrary numbers, image headers, and text strings often match valid CPU instruction encodings by pure chance, objdump will generate nonsensical assembly instructions that do not actually exist:

# Dangerous: Interprets plain data and text strings as processor instructions
objdump -D -j .rodata /usr/bin/target_app

Mitigation: Always restrict disassembly strictly to designated code sections using -d instead of -D, or explicitly specify -j .text.

3. Stripped Binaries and Base Address Offsets in PIE Binaries

When an executable has its symbol names stripped during release (strip -s), objdump will no longer display function names like <main>: or <OrderBook::process>. It will only show raw hexadecimal offsets under <disassembly of section .text>. Furthermore, modern Linux distributions compile programs as Position-Independent Executables (PIE). The offsets shown by objdump (typically starting at 0x1000 or 0x0) do not match the absolute memory addresses assigned at runtime by Address Space Layout Randomization (ASLR).

Mitigation: When diagnosing a crash address from dmesg, always calculate the relative offset by subtracting the base address from the reported instruction pointer. For detailed specifications on ELF internals, consult the Linux manual for elf(5) and the ArchWiki Debugging Guide. For comprehensive instruction reference tables, refer to the Intel 64 and IA-32 Architectures Software Developer's Manual.


Comparative Analysis: Binary Triage Utilities

The table below outlines when to reach for objdump compared to other standard Linux diagnostic tools:

Metric / Capability objdump readelf nm gdb
Primary Specialty Static Instruction Disassembly & Section Dumping Pure ELF Structural Header & Metadata Parsing Symbol Name & Address Enumeration Interactive Dynamic Execution & Memory Debugging
Parsing Engine GNU BFD (Binary File Descriptor Library) Native Direct ELF Parser (Zero BFD Dependency) GNU BFD Library GDB Process Control Engine / ptrace
Runtime Execution Required No (Static Analysis) No (Static Analysis) No (Static Analysis) Yes (Dynamic Attachment via ptrace)
Disassembly Quality High (Supports Intel/AT&T, Architecture Overrides) None (Displays Metadata Only) None (Symbol List Only) Full Interactive Frame-by-Frame Disassembly
Production Risk Zero (Read-Only Static Inspection) Zero (Read-Only Static Inspection) Zero (Read-Only Static Inspection) High (Can pause processes, inject latency, or alter state)

Today's Takeaway

Learning to read raw machine code directly from a compiled file gives you a fundamental advantage when standard observability tools fail. Right now, open a terminal on your computer and run this five-minute command:

objdump -d -M intel /bin/ls | head -n 35

Scroll to the entry point labeled <_start> and observe how your system initializes its CPU registers before passing control to the standard C library runtime. Seeing how raw hexadecimal numbers translate into direct hardware instructions turns compiled software from an intimidating black box into an open book. For a complete reference of supported flags and options, visit the official Linux man-pages: objdump(1).

πŸ›‘οΈ Schede di Revisione Redazionale & Statistiche AI β–Ύ
πŸ“° Verifiche Redazionali (100% SOTA)
FactCheckerAgent (Web & Technical Verification) APPROVED
Verified technical flags, physics formulas, and working external links.
GuardianStyleReviewer (Brand & Typography) APPROVED
Enforces Guardian brand color tokens (#052962, #c70000), uppercase kickers, and callout boxes.
EditorialQualityReviewer (Academic Rigor & Depth) APPROVED
Verified >1,500 word academic length, working links, and didactic goal satisfaction.
πŸ“Š Statistiche AI & Token Telemetry
Engine: gemini-3.6-pro
Auth: Google Gemini Ultra OAuth Session (~/.config/antigravity)
Prompt Tokens: 1,108
Completion Tokens: 6,198
Token Totali: 7,306
Costo API: $0.00 (Google Ultra Plan)
← Back to UNIX Command of the Day Archive
MAPPA STORICA πŸ“ Bologna