“So look with your heart, and not with your eyes.
A heart understandsm, a heart never lies.”

— Love Never Dies

Prologue

I guess this starts a new series in my blog, which record my debugging experience on some weird bugs, especially those deepen my understanding of the language. I know nowadays, AI makes writing and debugging code much easier, but I think it is still necessary to have the ability to understand.

So, without further ado, let’s begin.


Let’s Debug

Well, a briefing about the platform and compiler I used. Later for convenience, I will show the debugging process on Windows.

  • Windows 11 Pro + Visual Studio 2026 18.9.0
  • Ubuntu 20.04 + GCC 13.3.0

And by the way, it is assignment 6 from GAMES101, which is mainly about Bounding Volume Hierarchy (BVH) for ray casting.

The Symptom

It is an access violation, which attempts to read at 0x0. So, a guess is that a NULL pointer get dereferenced somewhere.

1
Exception thrown at 0x00007FF61B835100 in assignment_06.exe: 0xC0000005: Access violation reading location 0x0000000000000000.

Here is the code snippet that triggers the exception, marking the line where the exception is thrown.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class BVHAccel
{
// ...
BVHBuildNode* root;
}

Intersection BVHAccel::Intersect(const Ray& ray) const
{
Intersection intersection;
if (!root)
{
return intersection;
}
intersection = BVHAccel::getIntersection(root, ray); // <-- exception!
return intersection;
}

First analysis

Just by looking at the code above, one can tell this is no trivial bug. The exception does not occur at pointer dereference, but simply a function call. And if you inspect root and ray, none of them is NULL. What the?

If you are more experienced, you may realize there is another pointer involved here, which is this. Well, indeed, if you invoke method on a NULL pointer, an exception will also be thrown. Could it be? Well, not in this case. Since we are already inside BVHAccel::Intersect, this can’t be NULL. And for your reference, invoking a method on a NULL pointer will throw a different exception as given below.

1
2
Exception thrown: read access violation.
this was nullptr.

So, what could it be? There is no clue in the source code!

Behind the source code

If there is no clue in the source code, let’s dive deeper, into the assembly code.

A tip, in Visual Studio you can check the assembly code with “Go To Disassembly” feature, which is available in the right-click context menu.

Before we continue, here is some reminders of the basis, just in case.

  • 1 Byte = 8 bits
  • 1 Word = 2 Bytes
  • 1 DWord = 2 Word
  • 1 QWord = 2 DWord = 4 Word

And for Intel x86 assembly, here are something you need to know. Check x86 Assembly for more information.

image-20260818150458774
  • Integer arguments are passed in registers rcx, rdx, r8 and r9. (See x64 calling convention.)
  • mov dest, src: Copies the src operand into the dest operand, after which both operands contain the same contents.
  • lea dest, src: Load effective address, calculates the address of the src operand and loads it into the dest operand.
  • ecx can also be used with rep to repeats operation ecx times.

Here is the disassembled code of this suspicious function call. We can see that, because the returned struct is too large, BVHAccel::getIntersection returns an address of the value, then copy that to intersection.

1
2
3
4
5
6
7
8
9
10
11
12
    isect = BVHAccel::getIntersection(root, ray);
00007FF62E0450CB mov r9,qword ptr [ray] ; copy ray to r9
00007FF62E0450D3 mov rax,qword ptr [this] ; copy this to rax
00007FF62E0450DB mov r8,qword ptr [rax] ; copy rax (root) to r8
00007FF62E0450DE lea rdx,[rsp+68h] ; load rsp+104 to rdx
00007FF62E0450E3 mov rcx,qword ptr [this] ; copy this to rcx
00007FF62E0450EB call BVHAccel::getIntersection (07FF62E025668h)
00007FF62E0450F0 lea rcx,[intersection] ; load intersection to rcx
00007FF62E0450F5 mov rdi,rcx ; copy rcx to rdi
00007FF62E0450F8 mov rsi,rax ; copy rax (return value) to rsi
00007FF62E0450FB mov ecx,38h ; set repeat times to 0x38 = 56
00007FF62E045100 rep movs byte ptr [rdi],byte ptr [rsi] ; exception occurs here!

You may wonder where is root. Well, struct members are represented by offsets. Here root has offset 0 in BVHAccel, so happened to have the same address as this.

image-20260818151031448

Then, why set ecx to 0x38 (56) times? Well, that is the size of struct Intersection.

image-20260818150951012

Now, we can finally locate the source of the exception. It occurs at the last instruction rep movs, where rsi is 0x0!

Ah ha!

Now, things are much clearer. And in fact, the answer is a little too stupid. The returned rax is 0x0, which means… nothing is returned. So, I looked at BVHAccel::getIntersection, and found that I forgot to add return at the end of the function…

Well, you can’t blame the compiler or IDE, as they already warned you.

image-20260818151914466

After this, I changed the inspection severity of it from warning to error. 😅

After adding the missing return, everything is fine.


Reflection

Well, problem solved. But there is another interesting finding.

Except from the warning of “Not all control paths return a value”, another warning is about copy elision.

image-20260818160921156

I mentioned this in C++ Object Lifecycle when talking about return value optimization. So, if copy elision is applicable here, which means no extra copy is needed, does this become a silent bug?

Yes, it may. But it depends on the optimization. If the optimization happens not to touch rax, it may be some random address which is OK to access. But the result is almost never correct, because the instruction to set rax to intersection is never generated.


Epilogue

I didn’t expect this to take so long to write. It turns out I have to review a lot to finish this. Ever since AI coding agents get popular, seldom have I had this leisure to write code and debug by myself. ᓚᘏᗢ