The short description:
Setting a breakpoint on the first line of my
.CODE segment in an assembly program will not halt execution of the program.
What about Visual Studio's debugger would allow it to fail to create a breakpoint at the first line of a program written in assembly? Is this some oddity of the debugger, a case of breaking on a multi-byte instruction, or am I just doing something silly?
I have the following assembly program compiling and running in Visual Studio:
; Tell MASM to use the Intel 80386 instruction set. .386 ; Flat memory model, and Win 32 calling convention .MODEL FLAT, STDCALL ; Treat labels as case-sensitive (required for windows.inc) OPTION CaseMap:None include windows.inc include masm32.inc include user32.inc include kernel32.inc include macros.asm includelib masm32.lib includelib user32.lib includelib kernel32.lib .DATA BadText db "Error...", 0 GoodText db "Excellent!", 0 .CODE main PROC ;int 3 ; <-- If uncommented, this will not break. mov ecx, 6 ; <-- Breakpoint here will not hit. xor eax, eax ; <-- Breakpoint here will. _label: add eax, ecx dec ecx jnz _label cmp eax, 21 jz _good _bad: invoke StdOut, addr BadText jmp _quit _good: invoke StdOut, addr GoodText _quit: invoke ExitProcess, 0 main ENDP END main
If I try to set a breakpoint on the first line of the main function,
mov ecx, 6, it is ignored, and the program executes without stopping. Only will a breakpoint be hit if I set it on the line after that,
xor eax, eax, or any subsequent line.
I have even tried inserting a software breakpoint,
int 3, as the first line of the function, and it is also ignored.
The first thing I notice that is odd: viewing the disassembly after hitting one of my breakpoints gives me the following:
01370FFF add byte ptr [ecx+6],bh --- [Path]\main.asm xor eax, eax 00841005 xor eax,eax --- <-- Breakpoint is hit here _label: add eax, ecx 00841007 add eax,ecx dec ecx 00841009 dec ecx jnz _label 0084100A jne _label (841007h) cmp eax, 21 0084100C cmp eax,15h
What's interesting here is that the
xor is, in Visual Studio's eyes, the first operation in my program. Absent is the line
move ecx, 6. Directly above where it thinks my source begins is the line that actually sets
ecx to 6. So the actual start of my program has been mangled according to the disassembly.
If I make the first line of my program
int 3, the line that appears above where my code is in the disassembly is:
00F80FFF add ah,cl
As suggested in one of the answers, I turned off ASLR, and it looks like the disassembly is a little more stable:
.CODE main PROC ;mov ecx, 6 xor eax, eax 00401000 xor eax,eax --- <-- Breakpoint is present here, but not hit. _label: add eax, ecx 00401002 add eax,ecx --- <-- Breakpoint here is hit. dec ecx 00401004 dec ecx
The complete program is visible in the disassembly, but the problem still perists. Despite my program starting on an expected address, and the first breakpoint being shown in the disassembly, it is still skipped. Placing an
int 3 as the first line still results in the following line:
00400FFF add ah,cl
and does not stop execution, and re-mangles the view of my program in the disassembly again. The next line of my program is then at location
00401001, which I suppose makes sense because
int 3 is a one-byte instruction, but why would it have disappeared in the disassembly?
Even starting the program using the 'Step Into (F11)' command does not allow me to break on the first line. In fact, with no breakpoint, starting the program with F11 does not halt execution at all.
I'm not really sure what else I can try to solve the problem, beyond what I have detailed here. This is stretching beyond my current understanding of assembly and debuggers.
01370FFF add byte ptr [ecx+6],bh
At least I can explain away one mystery. Note the address, 0x1370fff. The CODE segment never starts at an address like that, segments begin at an address that's a multiple of 0x1000. Which makes the last 3 hex digits of the start address always 0. The debugger got confuzzled and started disassembling the code at the wrong address, off by one. The actual start address is 0x1371000. The disassembly starts off poorly because there's a 0 at 0x1370fff. That's a multi-byte ADD instruction. So it displays garbage for a while until it catches up with real machine code instructions by accident.
You need to help it along and give it a command to start disassembling at the proper address. In VS that's the Address box, type "0x1371000".
Another notable quirk is the strange value of the start address. A process normally starts at address 0x400000. You have a feature called ASLR turned on, Address Space Layout Randomization. It is an anti-virus feature that makes programs start at an unpredictable start address. Nice feature but it doesn't exactly help debugging programs. It isn't clear how you built this code but you need the /DYNAMICBASE:NO linker option to turn it off.
Another important quirk of debuggers you need to keep in mind here is the way they set breakpoints. They do so by patching the code, replacing the start byte of an instruction with an
int 3 instruction. When the breakpoint hits, it quickly replaces the byte with the original machine code instruction byte. So you never see this. This goes wrong if you pick the wrong address to set the breakpoint, like in the middle of a multi-byte instruction. It now no longer breaks the code, the altered byte messes up the original instruction. You can easily fall into this trap when you started with a bad disassembly.
Well, do this the Right Way. Start debugging with the debugger's STEP command instead.