1 of 61

Debugging as a Science

CSE 598 – Applied Program Analysis and Debugging

Fall 2025

​

Fish Wang

Arizona State University

2 of 61

How I Solved The First Module

2

3 of 61

Debugging Your Program

  • Observing an unexpected behavior
    • “My program is giving me a wrong result.”
  • Asking the right question
    • “Why isn’t my program deleting the right file?”
  • Making hypotheses
    • “This variable should hold that value…”
  • Examining the root cause
    • “Oh this is why!”

3

4 of 61

4

5 of 61

The Very First Step

  • Noticing the problem – something is wrong!
    • Crashes
    • Slowness
    • Weird behaviors

6 of 61

Explorer.exe Handle Leaks

  • Huh…

​

  • Huh?

​

​

​

  • Huh!

6

D:\> handle64.exe -p explorer.exe -s -nobanner

Handle type summary:

…

Key : 1780

…

Total handles: 9744

7 of 61

Explorer.exe Handle Leaks

  • Huh!
    • Hundreds of repeating entries

7

explorer.exe pid: 19428 type: Key 9C14: HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore

explorer.exe pid: 19428 type: Key 9C1C: HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\Windows.SortGroupsDescending

explorer.exe pid: 19428 type: Key 9C40: HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore

explorer.exe pid: 19428 type: Key 9C48: HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\Windows.Cancel

explorer.exe pid: 19428 type: Key 9C98: HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\Windows.SortGroupsAscending

...

8 of 61

What Tools Should You Use?

  • printf() debugging
    • A poor-man’s single step
    • But it can be useful at times!

​

    • Debuggers are not always available
    • Debugging multithreaded programs is difficult
    • Debugging asynchronous programs is difficult

8

9 of 61

What Tools Should You Use?

  • printf() debugging (cont.)
    • Q: Where should we printf?

​

    • Binary search ftw
    • Print at the location that a discrepancy is most likely observed

​

    • Ughhhhhhhh now I need to decide between the two!
    • I usually prefer binary search (when possible), because humans can be (overly) confident

9

10 of 61

What Tools Should You Use?

  • Debuggers
    • Many features for you to debug your programs in a fine-grained manner
      • Single stepping
      • Stepping over
      • Conditional breakpoints
      • Hardware watchpoints

​

    • Q: Where do we set breakpoints?

10

11 of 61

What Tools Should You Use?

  • Q: Are these tools good enough? Why or why not?

11

12 of 61

Requirements

  • Printf() debugging
    • The target program is changeable & recompilable
    • The target can be run multiple times
    • The bug is triggerable (somewhat) reliably
  • Debugger
    • The target program is debuggable (attachable, with the right permissions, etc.)
    • The target can be run multiple times
    • The bug is triggerable (somewhat) reliably

12

13 of 61

Requirements

  • Printf() debugging
    • The target program is recompilable
    • The target can be run multiple times
    • The bug is triggerable (somewhat) reliably
  • Debugger
    • The target program is debuggable (attachable, with right permissions, etc.)
    • The target can be run multiple times
    • The bug is triggerable (somewhat) reliably

13

14 of 61

Transient Bugs

  • Debugging temporary and unreproducible bugs is usually very challenging
    • You cannot reboot or rerun a long-running service
    • You cannot restart a program since it only runs once

14

15 of 61

Transient Bugs (cont.)

  • Debugging temporary and unreproducible bugs is usually very challenging
    • The bug is only triggered one in N times
    • The bug is difficult to measure or observe
      • Slowness
      • Multithreading-related issues
      • Carefully crafted attacks

15

16 of 61

Other Tools

  • Profilers
    • Diagnosing performance issues
    • Diagnosing bugs that don’t lead to crashes
    • Long-running processes

​

    • vTune Profiler, Luke Stackwalker, cProfile, vmprof, …

​

    • Hot spots
    • RPC delays
    • Waits

16

17 of 61

Slow Duplicati

  • Duplicati is a file backup utility written in .Net

17

https://github.com/duplicati/duplicati/issues/3167

18 of 61

Slow services.exe

  • services.exe on Windows 10 would occupy a CPU core for ~20 seconds every N minutes

18

19 of 61

Slow services.exe

19

20 of 61

Slow GTA Online

  • GTA Online has a slow JSON parser that counts the entire JSON string N times
    • The JSON string is usually more than 10 MB in size
    • N is the number of characters in the JSON string

20

https://nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times-by-70/

21 of 61

Slow GTA Online

21

https://nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times-by-70/

22 of 61

Slow UAC Dialog on Windows

  • Every time the UAC dialog pops up, it would spend ~30 seconds in grey screen before the dialog shows up

22

23 of 61

Slow UAC Dialog on Windows

  • Profiler (ETW + Windows Performance Analyzer) pinpoints the culprit for me
    • Acronis True Image registers a custom shell overlay to display indicator icons on each file (e.g., is the file backed up)
    • The callback function calls CreateToolhelp32Snapshot and Process32NextW to determine if another daemon process is running
    • Enumerating processes is slow, and the callback is invoked tens of thousands of times…

23

24 of 61

Slow UAC Dialog on Windows

24

Screenshot credit: https://randomascii.wordpress.com/2025/05/26/acronis-true-image-costs-performance-when-not-used/

25 of 61

Side Note: Reporting The Bug

25

26 of 61

Side Note: Reporting The Bug

26

27 of 61

Other Tools (cont.)

  • Logging
    • Diagnosing issues on long-running processes
    • Diagnosing issues without restarting the process

27

28 of 61

Broken Connections

  • The server and the client couldn’t connect to each other all of a sudden
  • Realizing that they are connected using OpenVPN
  • OpenVPN has logging support
  • Did OpenVPN establish connections properly?
  • No. Why?

​

28

29 of 61

Broken Connections (cont.)

  • Client side
    • Incoming TLS packets …
    • Connection failed. Retry in 30 seconds…

​

  • Server side
    • Incoming TLS request …
    • Request failure. Reason: CA expired.

​

29

30 of 61

Poor Man’s Logger (Profiler)

  • Tracers
    • Syscall tracers: strace
    • Library call tracers: ltrace, APISpy
    • Execution tracers: Intel Pin, Valgrind, DynamoRIO

30

31 of 61

31

32 of 61

Debugging Others’ Programs

  • Observing an unexpected behavior
    • “Fish’s program is giving me a wrong result.”
  • Asking the right question
    • “Why isn’t his program deleting the right file?”
  • Making hypotheses
    • Sadfyawpefspeaf9uqparjw/sKC?S:Ad;jfdsa
  • Examining the root cause
    • “Oh this is why!”

32

33 of 61

An Implicit Requirement

  • When debugging a program, you know
    • What the expected behavior is
    • Right vs wrong behaviors

​

  • Your understanding of the debuggee guides your debugging choices
    • Where to printf()
    • Where to set breakpoints
    • What to profile
    • What logs to read

33

34 of 61

An Implicit Requirement

  • When debugging a program, you know
    • What the expected behavior is
    • Right vs wrong behaviors

​

  • Your understanding of the debuggee guides your debugging choices
    • Where to printf()
    • Where to set breakpoints
    • What to profile
    • What logs to read

34

35 of 61

Understanding A Program

  • Q: How?

35

36 of 61

Understanding A Program

  • What not to do
    • Don’t start by reading source code
    • Don’t start from an arbitrary version of source code
    • Don’t start from line 0 (or the entry point)
    • Don’t strictly follow the code line by line

36

37 of 61

Understanding A Program

  • What you should do
    • Start by reading documentation, articles, blog posts, issues, user complaints, mailing lists, logging output, etc.
    • Make sure you are working on the right target and the right code base
    • Read source code by following data flows and logic flows

37

38 of 61

Understanding A Program

  • Your top priority
    • Solving a bug that you observed
    • Remember your goal when you are understanding the program, and always ask yourself, “is this part of logic relevant to my goal?”

38

39 of 61

Educated Guesses

  • While all truths are in the source code, understanding a large code base is still extremely difficult and time consuming
  • This is when we need to guess

​

  • Q: Guess what?

39

40 of 61

Educated Guesses

  • High-level logic of the program
    • Instead of reading the source code, you can come up with a hypothesis on the program logic, and then verify it by reading or debugging the source code

​

  • Low-level implementation details
    • Instead of reading the source code, you can come up with a hypothesis on each implementation detail, and then verify it

40

41 of 61

Educated Guesses

  • A rough expectation of the execution time or memory usage, and how they should grow when the size of input grows
    • It does not make any sense for Microsoft Word to use 10 GB of memory
    • It does not make sense for Unicorn Engine to run slower and slower on the same input
    • It does not make sense for ls to take more than 10 minutes to list files under /tmp/ … or does it?

41

42 of 61

Educated Guesses

  • Ultimately, the performance of a program is determined by its algorithms
    • Algorithmic complexity
      • Does the execution time or memory usage grow as expected?
    • Is it using the most optimal algorithm?

42

43 of 61

Variance Reduction

  • Only changing one variable (or one set of variables) and keeping all other variables constant and unchanged throughout one iteration of investigation
    • Changes in multiple variables may all influence the result. Which one is the culprit?

43

44 of 61

Binary Search

  • Multiple factors are potentially causing the problem. How can we quickly identify which one is the culprit?

44

45 of 61

Git Bisect

40b15f3164cca196f794bb2c4d47b1d0a7a7bf9b

ab187ac80deeeaf2e4e0d60562169556cfa1bacf

838cc653e4af003df00a4abf0c1ca60d063661dd

b0b456b794ed1b42c0292b2db8dd80820c8cd5d4

dc499fea726781a4e609d3429536fce7e7a902e2

515ab0d0bcb150939584f09959d4e279f2d8d956

d5391f96394a9415da1d50caa07e8dc99103050d

​

45

Broken

Works

?

46 of 61

ls /tmp/ is taking forever

  • There are many files under /tmp/. Listing them takes time (welp), but why don’t we see any intermediate output?

​

  • Guess: What does ls do?
    • Opens /tmp/ (opendir)
    • Loads file entries under /tmp/ (readdir)
    • Formats the output … with colors and columns

​

    • Q: What is the complexity of each step?

46

47 of 61

Complexity: Hard drives

  • Spinning drives and SSDs have different reading speed (and complexity)
    • Especially when reading small, fragmented files
    • Spinning drives: O(N)
    • SSDs: O(1)

​

    • Cache matters!

47

48 of 61

Complexity: File systems

  • Not all file systems are born equal
    • XFS is slow when dealing with many small files
    • NFS is extremely slow when dealing with many files (why?)
    • Fuse is slow, and you may be using it implicitly
      • ntfs-3g
    • Mapping directories or partitions into a virtual machine (although they are on the same host machine) can lead to extremely bad performance
      • Have you ever looked at their underlying implementations?

48

49 of 61

Complexity: Hidden behaviors

  • Antivirus
    • Stupid antivirus solutions would scan files that you are deleting
  • Failing hard drives
    • Reads may fail (taking too long, or returning erroneous data)
    • Writes may fail and block reads

49

50 of 61

Complexity: Formatting Output

  • It seems to be O(1)…
    • But in column output mode, ls must know the longest file name to determine column sizes!
    • This makes intermediate output impossible

50

51 of 61

Complexity: The Actual Output

  • Outputting to a console is not fast
    • cmd.exe or Windows Terminal are even slower
  • Outputting to a console over SSH or RDP is even slower

51

52 of 61

Where is the bottleneck?

  • Q: How do we determine the actual bottleneck(s)?

​

​

  • Q: What is the solution?

​

52

53 of 61

Key Debugging Steps

  • Making the bug reproducible
    • Eliminating indeterminism
  • Isolating the problem

Which process? Which executable? Which operation? Which input?

  • Finding control variables
    • Ideally, only one variable is changed each time!

53

54 of 61

Slow Docker

  • A Dockerized service is taking much much longer to finish on a server
    • What the service does: building software packages

​

​

54

55 of 61

Slow Docker

  • Analysis steps
    • The service itself?
    • Docker?
    • OS?
    • File system?
    • Hardware?

​

  • The final truth
    • SSDs were failing, leading to extremely low writing speed

55

56 of 61

Unicorn Engine Running Slower

  • Unicorn Engine is a user-mode machine code emulator based on Qemu
  • It runs machine code in an emulated environment
  • Problem: The more code we ran, the slower each run became
  • This is definitely not the expected behavior (does your CPU work like this?)

56

57 of 61

Unicorn Engine Running Slower

  • What happened?
    • Educated guess: Unicorn Engine is pretty much just an emulator. It does not really wait long for any syscalls.
    • Educated guess: No multithreading
    • Educated guess: It’s probably caused by some sort of data structure with an O(N) lookup complexity (e.g., a linked list).
    • Hypothesis: Certain functions must be taking longer and longer each time we run code inside

57

58 of 61

Unicorn Engine Running Slower

  • What happened?
    • Making the problem reproducible: Writing a minimal test case to reproduce the problem reliably.�I developed a small timer function in C to time arbitrary functions.
    • Isolating the issue: Binary search in all functions that are called, following the call tree. Observe which function takes longer and longer.
    • Control variable: The number of machine code snippets that we execute in Unicorn Engine.

58

59 of 61

Unicorn Engine Running Slower

  • Finally
    • Unicorn Engine abuses the device mapping mechanism in Qemu to map memory regions and memory pages
    • Qemu does not seem to support freeing device mapping regions, which is usually not a problem since you rarely plug and unplug devices when a system runs
    • In Unicorn Engine, emulated programs may allocate or deallocate memory regions all the time!

59

60 of 61

Unicorn Engine Running Slower

  • Finally (cont.)
    • All memory regions are put together in a linked list. Without removing regions out of this linked list ever, allocating new regions becomes slower and slower.
    • A fast-lookup view (FlatView) is supposed to be offering fast lookups. But it’s never reconstructed after a memory region is freed…

60

https://github.com/unicorn-engine/unicorn/pull/655

61 of 61

Questions?

61