Source-Level vs. Assembly-Level Debuggers
Both source-level and assembly-level debuggers are used for debugging programs, but they differ significantly in what they show and how they are used. Hereâs a side-by-side comparison:
Feature | Source- Level | Assembly-Level |
---|---|---|
View | Displays the original source code (e.g., C/C++, Python). | Displays machine instructions (assembly code). |
Symbols | Uses variable/function names, line numbers, and data types. | Often lacks symbols; uses addresses and offsets. |
Ease of Use | User-friendly, intuitive; ideal for high-level debugging. | Requires understanding of assembly and processor architecture. |
Breakpoints | Set breakpoints on source lines or function names. | Set breakpoints at instruction addresses. |
Variables | Shows variable names, values, and types directly. | Shows registers, memory, and stack contents, not high-level variables. |
Use Case | Best for developers with access to source code and debugging high-level logic. | Ideal for reverse engineering, malware analysis, kernel debugging, or when source is unavailable. |
Tools | Examples: GDB (with symbols), Visual Studio Debugger, LLDB, WinDbg (with PDBs). | Examples: OllyDbg, x64dbg, IDA Pro (debugger), Ghidra (runtime), WinDbg (assembly view). |
Strengths & Limitations | Cannot debug stripped binaries or deeply obfuscated code. | Full control; reveals exact program behavior at runtime. |
Common in | Application development, software testing. | Low-level debugging, exploit development, binary analysis. |
When to Use Each
- Use Source-Level Debugger when:
- You are developing or debugging your own program.
- You have access to source and debug symbols.
- You need to debug high-level logic or variable state.
- Use Assembly-Level Debugger when:
- Youâre analyzing compiled code without source.
- Youâre doing reverse engineering or malware analysis.
- You need precise control and visibility over low-level behavior.
Kernel-Mode vs. User-Mode Debugging
When debugging software on Windows or other operating systems, understanding the difference between kernel-mode and user-mode debugging especially in reverse engineering, driver development, or low-level systems work.
Basic Definitions
Debugging Mode | Description |
---|---|
User-Mode | Debugging regular applications that run in user space, isolated from the OS kernel. |
Kernel-Mode | Debugging code that runs in the operating system core, such as device drivers or the kernel itself. |
Key Differences
Feature | User-Mode Debugging | Kernel-Mode Debugging |
---|---|---|
Code Being Debugged | Applications (.exe, .dll) | OS components, device drivers, interrupts, kernel data structures |
Memory Access | Can access only user space memory | Can access entire system memory, including user and kernel memory |
Crash Impact | Crashes only the target application | Can crash or hang the entire system (Blue Screen of Death - BSOD) |
Debugger Tools | GDB, Visual Studio Debugger, x64dbg, OllyDbg | WinDbg (kernel mode), GDB with qemu/vm, VMware GDB Stub |
Symbols | Uses PDBs or DWARF for app symbols | Uses Microsoft symbol server, custom PDBs for drivers |
Setup Complexity | Simple. Can debug locally | Complex. Often requires remote debugging, VMs, or serial pipes |
Use Cases | Malware analysis, app bugs, performance tuning | Driver development, rootkit detection, OS-level reverse engineering |
Privileges Required | Standard user/admin (depends on debugger) | Requires admin privileges and kernel debugger configuration |
Isolation | Runs in a safe, isolated mode | Runs in privileged mode; debugging errors can cause serious issues |
When to Use
- User-Mode Debugging:
- Debugging app crashes, logic errors, or malware with no kernel-level component.
- You have the executable or source code of the application.
- Kernel-Mode Debugging:
- Debugging device drivers, kernel exploits, or reverse engineering rootkits.
- You need to inspect kernel memory, system calls, or privileged instructions.
Degugging
Single-Stepping
Single-stepping is a core debugging feature that allows you to execute one instruction or line of code at a time, so you can closely observe what your program is doing at each step.
Itâs used to:
- Track how variables change.
- See which paths the program takes (especially in conditions or loops).
- Catch the exact instruction where an error or crash happens.
- Understand complex or unknown code, like in reverse engineering.
Example
(C Code)
int a = 5;
int b = 10;
int c = a + b; // â break here and start stepping
printf("%d\n", c);
If you single-step here:
- Youâll see
a = 5
assigned. - Then
b = 10
. - Then
c = 15
gets computed. - Then the output happens.
Assembly:
In tools like x64dbg or WinDbg, single-stepping is even finer-grained:
mov eax, 5
add eax, 3
call printf
You can step through each instruction, watching how EAX
changes in the CPU register window.
**Stepping-Over and Stepping-Into
When Single-Stepping through code, especially in reverse engineering or software debugging, itâs important to understand how you step through functions:
Step-Into
- What it does: Enters the function being called.
- When to use:
- You want to analyze the inner workings of a function.
- You suspect that the function contains important or malicious behavior.
- What youâll see: The very first instruction of the called function.
Example:
doWork(); // â Step Into
Youâll land at the first instruction inside doWork()
.
Step-Over
- What it does: Executes the function without entering it.
- When to use:
- You trust the function or know itâs not critical (e.g.,
printf
,LoadLibrary
). - You want to save time by skipping irrelevant internal operations.
- You trust the function or know itâs not critical (e.g.,
- What youâll see: The instruction right after the function call.
Example:
doWork(); // â Step Over
Youâll skip over the function internals and land after the call.
Risks of Stepping Over
- Missed logic: You might miss malware or custom behavior hidden inside a function.
- Functions that never return: If a function like
ExitProcess()
or a malicious loop is stepped over, your debugger may lose control, and youâll be stuck.
Pausing Execution with Breakpoints
Breakpoints are critical tools in any debugger that allow you to pause program execution and examine the current stateâregisters, memory, stack, etc.âat a precise location.
Why Use Breakpoints?
- While a program runs, registers and memory are constantly changing.
- A breakpoint halts execution at a specific instruction so you can analyze whatâs happening at that moment.
- Especially useful when you want to inspect:
- What values are in registers (like
EAX
) - What arguments are being passed to a function (like
CreateFileW
) - What data is about to be encrypted or sent over the network
- What values are in registers (like
Types of Breakpoints
1. Software Execution Breakpoints
- How it works: Replaces the first byte of an instruction with
0xCC
(INT 3). - Purpose: Stops execution when a specific instruction is executed.
- Example:
- In disassembly:
00401130 push ebp
becomes00401130 CC
in memory.
- In disassembly:
- Risks:
- Can be detected or altered by anti-debugging techniques.
- If the code modifies itself or verifies its code section, the
0xCC
will cause issues.
2. Hardware Execution Breakpoints
- How it works: Uses special debug registers (DR0âDR3) in the CPU to monitor for a specific address.
- Benefits:
- Doesnât modify the program code.
- Can watch for read/write or execution.
- Perfect for self-modifying or anti-debugging code.
- Limitations:
- Only 4 hardware breakpoints available.
- Can be manipulated by malware using debug register access.
đĄ Protection: Set the General Detect flag in DR7 to trap unauthorized debug register modifications.
3. Conditional Breakpoints
- How it works: Triggers only if a specified condition is true.
- Example:
- Break only if
GetProcAddress("RegSetValue")
is called. - Condition: check the value at the top of the stack or in a register.
- Break only if
- Downside:
- Slows down execution significantly if overused.