Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debug breakpoint is hit when it shouldn't be #19443

Open
lawrence-laz opened this issue Mar 26, 2024 · 7 comments · May be fixed by #20543
Open

Debug breakpoint is hit when it shouldn't be #19443

lawrence-laz opened this issue Mar 26, 2024 · 7 comments · May be fixed by #20543
Labels
backend-llvm The LLVM backend outputs an LLVM IR Module. bug Observed behavior contradicts documented or intended behavior debug-info Debug information of binary generated by Zig is not as expected.
Milestone

Comments

@lawrence-laz
Copy link

lawrence-laz commented Mar 26, 2024

Zig Version

0.12.0-dev.2809+f3bd17772
0.12.0-dev.3439+31a7f22b8

Steps to Reproduce and Observed Behavior

Create a simple repro by running:

mkdir repro && cd repro
zig init
cat <<EOT > src/main.zig
const std = @import("std");

pub fn main() !void {
    var i: usize = 0;
    while (i < 100) : (i += 1) {
        if (i == 90) {
            std.debug.print("It's 90!", .{});
        } else if (i == 50) {
            std.debug.print("It's 50!", .{});
        }
    }
}
EOT
zig build
cd zig-out/bin/
lldb repro

Then in lldb console:

(lldb) b main.zig:9
Breakpoint 1: where = repro`main.main + 188 at main.zig:9:28, address = 0x0000000100000824
(lldb) run
Process 4864 launched: '~/zig/repro/zig-out/bin/repro' (arm64)
Process 4864 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100000824 repro`main.main at main.zig:9:28
   6            if (i == 90) {
   7                std.debug.print("It's 90!", .{});
   8            } else if (i == 50) {
-> 9                std.debug.print("It's 50!", .{});
   10           }
   11       }
   12   }
Target 0: (repro) stopped.
(lldb) var i
(unsigned long) i = 0
(lldb) continue
Process 4864 resuming
Process 4864 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100000824 repro`main.main at main.zig:9:28
   6            if (i == 90) {
   7                std.debug.print("It's 90!", .{});
   8            } else if (i == 50) {
-> 9                std.debug.print("It's 50!", .{});
   10           }
   11       }
   12   }
Target 0: (repro) stopped.
(lldb) var i
(unsigned long) i = 1

Notice how the breakpoint gets hit every cycle, even though the i == 50 should happen only once. Reading the i variable it prints 0, 1, etc.

Expected Behavior

Breakpoint should hit only once when i == 50.

@lawrence-laz lawrence-laz added the bug Observed behavior contradicts documented or intended behavior label Mar 26, 2024
@mlugg
Copy link
Member

mlugg commented Mar 26, 2024

I think this is an manifestation of the slightly weird issue that when a block is skipped, debuggers will indicate us first jumping to the block's last line, and then we appear to enter the following code.

@lawrence-laz
Copy link
Author

lawrence-laz commented Mar 26, 2024

You might be onto something.

If the code is changed to:

const std = @import("std");

pub fn main() !void {
    var i: usize = 0;
    while (i < 100) : (i += 1) {
        if (i == 90) {
            std.debug.print("It's 90!", .{});
        } else if (i == 50) {
            const foo: usize = i + 10;
            var bar: usize = foo + 20;
            std.debug.print("It's inner {d}!", .{bar});
            bar += 1;
            std.debug.print("It's inner plus one {d}!", .{bar});
            std.debug.print("It's 50!", .{});
        }
    }
}

And breakpoint is in the middle of the block, then it behaves as expected:

(lldb) b main.zig:11
Breakpoint 2: where = repro`main.main + 320 at main.zig:11:28, address = 0x00000001000008a8
(lldb) run
Process 5813 launched: '~/zig/repro/zig-out/bin/repro' (arm64)
Process 5813 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
    frame #0: 0x00000001000008a8 repro`main.main at main.zig:11:28
   8            } else if (i == 50) {
   9                const foo: usize = i + 10;
   10               var bar: usize = foo + 20;
-> 11               std.debug.print("It's inner {d}!", .{bar});
   12               bar += 1;
   13               std.debug.print("It's inner plus one {d}!", .{bar});
   14               std.debug.print("It's 50!", .{});
Target 0: (repro) stopped.
(lldb) var i
(unsigned long) i = 50

Would this mean zig cannot do anything and it's the issue of a debugger?
Maybe zig could output some noop debug info at the end of the block, so it wouldn't overlap with the last line in source code?

@nektro
Copy link
Contributor

nektro commented Mar 26, 2024

does this reproduce on 0.12.0-dev.3439+31a7f22b8 ?

@lawrence-laz
Copy link
Author

lawrence-laz commented Mar 26, 2024

Tried reproducing same with c++:

mkdir repro-cpp
cd repro-cpp
cat <<EOF > main.cpp
#include <iostream>

int main()
{
    int i = 0;
    while (i < 100)
    {
        if (i == 90) {
            std::cout << "It's 90!\n";
        }
        else if (i == 50) {
            std::cout << "It's 50!\n";
        }
        i += 1;
    }
}
EOF
g++ -g main.cpp
lldb a.out

and then in debugger

(lldb) b main.cpp:12
Breakpoint 1: where = a.out`main + 124 at main.cpp:12:23, address = 0x0000000100003128
(lldb) run
Process 6746 launched: '~/zig/repro/repro-cpp/a.out' (arm64)
Process 6746 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100003128 a.out`main at main.cpp:12:23
   9                std::cout << "It's 90!\n";
   10           }
   11           else if (i == 50) {
-> 12               std::cout << "It's 50!\n";
   13           }
   14           i += 1;
   15       }
Target 0: (a.out) stopped.
(lldb) var i
(int) i = 50

seems to work fine, so it's probably not a debugger issue?

@lawrence-laz
Copy link
Author

lawrence-laz commented Mar 26, 2024

does this reproduce on 0.12.0-dev.3439+31a7f22b8 ?

@nektro tested on 0.12.0-dev.3439+31a7f22b8, same issue

@mlugg
Copy link
Member

mlugg commented Mar 27, 2024

Would this mean Zig cannot do anything and it's the issue of a debugger?

No, this is still a Zig bug. I might try and take a look later today.

@andrewrk andrewrk added this to the 0.14.0 milestone Mar 28, 2024
@Vexu Vexu added the debug-info Debug information of binary generated by Zig is not as expected. label Jun 10, 2024
tau-dev added a commit to tau-dev/zig that referenced this issue Jul 8, 2024
Reset the line number to its previous state after genBodyDebugScope, so
that instructions between its end and the next dbg_stmt do not wander
inside the block. This core fix is very simple, but surfaces a secondary
issue: the LLVM codegecn generates blocks in a different order than
Clang would for the same CFG in C, which ultimately results in GDB
skipping the loop header altogether. So we need to reorder some basic
blocks too.
tau-dev added a commit to tau-dev/zig that referenced this issue Jul 8, 2024
Reset the line number to its previous state after genBodyDebugScope, so
that instructions between its end and the next dbg_stmt do not wander
inside the block. This core fix is very simple, but surfaces a secondary
issue: the LLVM codegen generates blocks in a different order than Clang
would for the same CFG in C, which ultimately results in GDB skipping
the loop header altogether. So we need to reorder some basic blocks too.
tau-dev added a commit to tau-dev/zig that referenced this issue Jul 8, 2024
Reset the line number to its previous state after genBodyDebugScope, so
that instructions between its end and the next dbg_stmt do not wander
inside the block. This core fix is very simple, but surfaces a secondary
issue: the LLVM codegen generates blocks in a different order than Clang
would for the same CFG in C, which ultimately results in GDB skipping
the loop header altogether. So we need to reorder some basic blocks too.
tau-dev added a commit to tau-dev/zig that referenced this issue Jul 8, 2024
Reset the line number to its previous state after genBodyDebugScope, so
that instructions between its end and the next dbg_stmt do not wander
inside the block. This core fix is very simple, but surfaces a secondary
issue: the LLVM codegen generates blocks in a different order than Clang
would for the same CFG in C, which ultimately results in GDB skipping
the loop header altogether. So we need to reorder some basic blocks too.
tau-dev added a commit to tau-dev/zig that referenced this issue Jul 21, 2024
Reset the line number to its previous state after genBodyDebugScope, so
that instructions between its end and the next dbg_stmt do not wander
inside the block. This core fix is very simple, but surfaces a secondary
issue: the LLVM codegen generates blocks in a different order than Clang
would for the same CFG in C, which ultimately results in GDB skipping
the loop header altogether. So we need to reorder some basic blocks too.
tau-dev added a commit to tau-dev/zig that referenced this issue Jul 21, 2024
Reset the line number to its previous state after genBodyDebugScope, so
that instructions between its end and the next dbg_stmt do not wander
inside the block. This core fix is very simple, but surfaces a secondary
issue: the LLVM codegen generates blocks in a different order than Clang
would for the same CFG in C, which ultimately results in GDB skipping
the loop header altogether. So we need to reorder some basic blocks too.
@andrewrk andrewrk added the backend-llvm The LLVM backend outputs an LLVM IR Module. label Aug 8, 2024
@andrewrk andrewrk modified the milestones: 0.15.0, 0.14.0 Aug 8, 2024
tau-dev added a commit to tau-dev/zig that referenced this issue Sep 4, 2024
Reset the line number to its previous state after genBodyDebugScope, so
that instructions between its end and the next dbg_stmt do not wander
inside the block. This core fix is very simple, but surfaces a secondary
issue: the LLVM codegen generates blocks in a different order than Clang
would for the same CFG in C, which ultimately results in GDB skipping
the loop header altogether. So we need to reorder some basic blocks too.
@RoadRoller01
Copy link

RoadRoller01 commented Jan 28, 2025

Your example is little bit verbose,
this is a simpler example that addresses the problem,

const std = @import("std");

pub fn main() !void {
    var argslen = std.os.argv.len; // or std.crypto.random.int(u8)

    if (argslen == 0xFEED) {
        std.debug.print("you are not on the end", .{});
        argslen += 0x69;
    }
}

When tracing such problem you must walk instruction by instruction and look
its accosted line, Thus you don't get unexpected behavior on different debuggers1,
So lets have some walk

$ llvm-objdump --disassemble-symbols=main.main -S ./zig-out/bin/zig-test --disassembler-color=on | less -R
.
.
.
;     if (argslen == 0xFEED) {
 104b10c: f85f83a8      ldur    x8, [x29, #-0x8] 
 104b110: 529fdda9      mov     w9, #0xfeed             // =65261
 104b114: eb090108      subs    x8, x8, x9
 104b118: 540000c0      b.eq    0x104b130 <main.main+0x44>
 104b11c: 1400000d      b       0x104b150 <main.main+0x64>  // not equal?  jump to 104b150
 104b120: 2a1f03e0      mov     w0, wzr     // So i jumped inside the source code if statement to jump 
to the next instruction Also lldb have outputted warning: "Note: this address is compiler-generated code in function main.main that has no source code associated with it."  
;         argslen += 0x69;
 104b124: a9427bfd      ldp     x29, x30, [sp, #0x20]  // Wow we are back inside the if statement
 104b128: 9100c3ff      add     sp, sp, #0x30
 104b12c: d65f03c0      ret                                        // and return cool 
 104b130: f9400be0      ldr     x0, [sp, #0x10]
;         std.debug.print("you are not on the end", .{});
 104b134: 9400000d      bl      0x104b168 <debug.print__anon_1693>
;         argslen += 0x69;
 104b138: f85f83a8      ldur    x8, [x29, #-0x8]
 104b13c: b101a508      adds    x8, x8, #0x69
 104b140: f90007e8      str     x8, [sp, #0x8]
 104b144: 1a9f37e8      cset    w8, hs
 104b148: 37000068      tbnz    w8, #0x0, 0x104b154 <main.main+0x68>
 104b14c: 14000004      b       0x104b15c <main.main+0x70>
 104b150: 17fffff4      b       0x104b120 <main.main+0x34>    // great another jump wait a second why are we inside the source code if statement weird, okay i will jump to 104b120
 104b154: f9400be0      ldr     x0, [sp, #0x10]
;         argslen += 0x69;
 104b158: 94000069      bl      0x104b2fc <debug.FullPanic((function 'defaultPanic')).integerOverflow>
 104b15c: f94007e8      ldr     x8, [sp, #0x8]
;         argslen += 0x69;
 104b160: f81f83a8      stur    x8, [x29, #-0x8]
 104b164: 17ffffef      b       0x104b120 <main.main+0x34>

These what i gathered for today

Footnotes

  1. Walking by line in lldb cause the problem, but gdb doesn't, They got difference implementation for waling by line.

@andrewrk andrewrk modified the milestones: 0.14.0, 0.15.0 Feb 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend-llvm The LLVM backend outputs an LLVM IR Module. bug Observed behavior contradicts documented or intended behavior debug-info Debug information of binary generated by Zig is not as expected.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants