-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Clean up debug.zig, particularly stack trace formating #8228
Conversation
Note that a bunch of the overrides have now been removed to a separate PR. Here are all of the new overrides (better names/design would be appreciated): const std = @import("std");
pub const debug_config = struct {
pub const SymbolMap = struct {
const SymbolInfo = std.debug.SymbolInfo;
pub fn addressToSymbol(self: *@This(), address: usize) !SymbolInfo {
@compileError("Unimplemented");
}
pub fn init(allocator: *std.mem.Allocator) !@This() {
@compileError("Unimplemented");
}
pub fn deinit(self : *@This()) void {
@compileError("Unimplemented");
}
};
pub fn captureStackTraceFrom(
allocator: *std.mem.Allocator,
first_address: ?usize,
base_pointer: ?usize,
stack_trace: *std.builtin.StackTrace,
) !void {
@compileError("Unimplemented");
}
}; |
I generally like this idea but I'd take it a step further. fn collectStackTrace(allocator: *std.mem.Allocator, trace *StackTrace) !void; The allocator part is needed for some strategies (eg. I've implemented a DWARF unwinder and it needs to parse quite a bit of things) while Nits about
|
lib/std/debug.zig
Outdated
} | ||
|
||
/// TODO multithreaded awareness | ||
var self_debug_info: ?DebugInfo = null; | ||
var symbol_at_address: ?AddrSymMapping = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While we're at it let's consider if this global cache should stay, the hard part is deciding when to deinitialize it.
I think this is better, I went ahead and renamed. |
This function already (sort of) exists. It's called Lines 144 to 150 in ce14bc7
It only captures up to the number of addresses in the passed in StackTrace and it doesn't take in a allocator.
If we always collect the addrs before printing (instead of iterating and printing as we get them), then we will require allocation which isn't actually needed. I don't know how much this matters, but it might be a good idea to avoid allocation where possible in panic because the panic could be due to OOM. I suppose allocation will need to happen for the debug info anyway if that hasn't already been opened. One possible solution would be to have a fixed max number of addresses (maybe 1024) and use an array instead. Note that I think that an allocator should be passed in, I am just wondering if we should try to avoid using it when possible. Always collecting would be considerably cleaner than the current approach because we could merge together a bunch of different pretty similar functions. The collect function would also need to take |
Done
No, this can't be done. The |
Yes, I'm proposing to make it part of the API and make it flexible enough to handle the many frame collection strategies.
Some non-trivial unwinding mechanisms need an allocator.
The collection happens in a user provided buffer (the
It really depends on the kind of OOM, if you really have no more memory left we're going to crash and burn anyway (or the kernel, or some other piece).
Yep, that's the idea.
That's the plan!
This is a bit of a hack needed by the current frame walker and its inability to find its way past the signal handler frame. |
Hmm, passing this allocator is proving to be a real PITA in |
I split out the extra overrides into a new PR rgreenblatt#1. Reviewing this new PR can wait until this is merged... |
lib/std/meta.zig
Outdated
/// Get a decl on T by indexing into each field. Returns null if one of the | ||
/// sub-decls doesn't exist, `compileError` if a sub-decl other than the last one | ||
/// is a value instead of a type. | ||
pub fn lookupDecl(comptime T: type, comptime names: []const []const u8) ?(lookupDeclType(T, names) orelse type) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also added this utility function. This is generally useful for the if (@hasDecl(root, "name")) root.name else default
pattern. This ended up being considerably grosser to implement than I expected and I am considering removing it.
fn readElfDebugInfo(allocator: *mem.Allocator, elf_file: File) !Self { | ||
nosuspend { | ||
const mapped_mem = try debug_info.mapWholeFile(elf_file); | ||
const hdr = @ptrCast(*const elf.Ehdr, &mapped_mem[0]); | ||
if (!mem.eql(u8, hdr.e_ident[0..4], "\x7fELF")) return error.InvalidElfMagic; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While looking into how to properly turn things into interfaces, I noticed that a lot of the error handling is a bit odd.
It seems like most of the errors which could be bubbled up via addressToSymbol
are really just symptoms of the debug symbols being invalid. For instance, this InvalidElfMagic
error would be bubbled up and printed out halting printing the rest of the stack trace. This will only only occur if elf_file
is invalid.
So how do we want to handle errors like this? Silently swallow? Bubble them up and stop printing? Note that this function is private and only used for SymbolMap
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an aside, #2647 would be useful for making the interface simple if wanted to support many "types" of errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the moment, I have just used anyerror
. This should (probably?) be changed before merge.
@LemonBoy Are you still reviewing this? Or should some else? (no rush of course). |
What's the status of this pr? There hasn't been any work on it in nearly three months, there are a bunch of conflicts, it seems unfinished and it looks like the goal shifted during development. It is also hard to review with so many commits. Would you mind opening a new pr rebased on master with a bit cleaner history if you are still interested in working on this? |
I was waiting on @LemonBoy to respond - at some point he mentioned that he might want to start a totally separate PR doing some of the same things and I didn't want to waste effort. He never responded and I forgot to bump, so here we are... This is definitely unfinished with several design and implementation issues. I will probably open a new PR rebased off of master today or tomorrow. I will try to summarize the outstanding issues etc. Hopefully this time the goals of the PR won't shift during implementation... |
Respond to what? GH's PR interface makes it extremely hard to see what's going on.
I'm eventually going to add a new unwinder (soon ™️) and will need parts of this changeset or have to implement them myself, coordination is the key.
The initial "Allow for overriding a variety of different stack trace functions" was quite underwhelming as it merely allowed to swap out bits and pieces of the debug infrastructure in a rather inelegant and inflexible way. Having a wee bit of experience with Zig and its debug infrastructure I tried to explain how to fix some of the shortcomings and, after some back and forth, the code is starting to look nice (even though I'm still not sold on the use of That's the whole point of a code review, discussing the changes with the big picture in mind. If you just want a "LGTM 👍" then ask for another reviewer. |
Alright I think it's time for this to be closed and any improvements extracted out of it into smaller, more easily reviewable and mergeable pieces. I look forward to those PRs if you still have the time & energy to make them :) |
See above three mentioned PRs. Sorry about the wait. Those PRs don't include quite everything from here, but I will wait for #9252 to be merged or at least more polished before creating the final PR(s). |
The idea with this PR is to allow for overriding at a more granular level than just overriding panic. This is useful for the freestanding context so you don't have to implement all of the necessaryos
functions. It also might be useful when on a supported OS because it allows for more precise overriding. This should allow for easier usage ofstd.testing
,GeneralPurposeAllocator
, andpanicExtra
in the freestanding context.This PR is now 95% clean up of the debug interface and only adds the ability to overriding the mapping of addresses to symbols and how stack traces are collected. This will allow for easer usage of
std.testing
andpanicExtra
, but notGeneralPurposeAllocator
.See #8228 (comment) for what a full config looks like.I have also heavily refactored how
std.debug
is layed out including removingprintSourceAtAddress
.I believe there are no other changes (other than additions) to the public interface of std.debug, but I could be wrong.This PR also changes the name ofgetStderrMutex
togetPrintMutex
(because the writer can now be overriden), makes it so thatcaptureStackTrace
takes an allocator and can return an error, and changes the inputs ofpanicExtra
. I also cleaned up a bunch of duplicated code related to dumping stack traces.This functions adds the ability to override the writer used bystd.debug
. As such, we must also add the capacity to overridedetectTTYConfig
.I went ahead and also added these capabilites toSee here: #8228 (comment)std.log
.Related: #8214
TODO: