Logging C Functions
justine.lol
justine.lol
You could just log the base address of the executable and the relative addresses of the functions instead. The conversion from a relative address to the function's name can take place later, as part of a post-processing step. Should further reduce runtime overhead.
It's a commercial product but in my usage/experience worth every penny: https://www.highintegritysystems.com/tools/tracealyzer/
Mmm... Tryptamine...
I think Justine goes like on an inward self search or something for personally echoing creative landmarks in the overall late eighties early nineties PC scene, the posts we're all eagerly waiting for, and when she finds one then just stops waiting :-)
Very inspiring overall, I'm eagerly waiting for the next one, but in the meantime let me enjoy this one.
This is one place where reversible debuggers shine. Try rr[1].
/*
gcc -g -Wall -o x x.c
gdb ./x
(gdb) r
(gdb) bt
#0 0x0000000000000000 in ?? ()
#1 0x0000555555554617 in foo () at x.c:6
#2 0x0000555555554628 in main () at x.c:10
(gdb) f 1
#1 0x0000555555554617 in foo () at x.c:6
6 bar();
(gdb) p bar
$1 = (void (*)(void)) 0x0
*/
#include <stddef.h>
void (*bar)(void) = NULL;
void foo() {
bar();
}
int main() {
foo();
} #include <string.h>
void foo() {
int array[1];
memset(array, 0, 100); /\* Oh no, trash the stack! */
}
int main() {
foo();
}
This gets us: Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
(gdb) bt
#0 0x0000000000000000 in ?? ()
#1 0x0000000000000000 in ?? ()
But with a time travel debugger (I'm using UDB because - disclaimer - it's what I work on. `rr` would work just as well): Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
recording 10,617> backtrace
#0 0x0000000000000000 in ?? ()
#1 0x0000000000000000 in ?? ()
^ Because we returned to NULL we have segfaulted but the stack is also trashed due to the memset. We don't know how we got here. recording 10,617> reverse-stepi
0x0000000000401146 6 }
99% 10,616> bt
#0 0x0000000000401146 in foo () at smash.c:6
#1 0x0000000000000000 in ?? ()
^ We've stepped back before the return, so we can now see how we got to NULL. Still incomplete stack because it's still trashed. 99% 10,616> reverse-step
5 memset(array, 0, 100); /* Oh no, trash the stack! \*/
99% 10,586> bt
#0 foo () at smash.c:5
#1 0x0000000000401155 in main () at smash.c:9
99% 10,586>
^ We've gone back before the stack smash happened, so now we get the full backtrace.It sounds a little like they re-invented Detours.
Use nops (or other placeholders to insert "counter increment" fills) on instrumented runs, e.g. in test, that way you don't have to modify the "production" code to measure test coverage.
Based on the opening of TFA, my assumption is that they could, but just haven't yet? Is this something we can hope to be added by more mainstream projects? Or are there technical or cultural blockers I can't see?
Instrumentation, tracing (for both logging and performance analysis), stack traces and runtime analysis are absolutely not new concepts. There are no "cultural blockers" - this stuff is already used everywhere, and has been for many decades now.
There are countless ways to implement those mechanisms. This blog post presents yet another one. There are numerous tools in this space already - across all programming languages, different kinds of hardware and operating systems, and IDEs.
One typo, which is a bit confusing until you look at the source code to confirm it's actually --ftrace: "The Cosmopolitan Libc _start() function starts by intercepting the --strace flag"
There's also a great Windows tool for this kind of tracing, WinAPIOverride. http://jacquelin.potier.free.fr/winapioverride32/
Incurring a 1 us overhead on each function call is very steep if you are doing a function entry/exit trace and nearly totally smears the profiling information you could get. In contrast, efficient recompilation-based instrumentation should only incur maybe 100 ns down to maybe around 10 ns depending on how aggressively you instrument and how much overhead you are willing to incur in the logging disabled case. In aggregate, a efficient recompilation-based approach should only incur a whole program overhead in the low double digit percent range when enabled and at most a low single-digit percent, if even that, when disabled. As a corollary, if 1/10th the per-invocation overhead results in say a aggregate 30% overhead, then we can reasonably assume the full overhead case is around 10x as much overhead resulting in 300% aggregate overhead, or a program taking 4x as long to run. That is a qualitatively different amount of overhead.
[1] http://dtrace.org/blogs/brendan/2011/02/18/dtrace-pid-provid...
Most of the overhead comes from the fact that it's using kprintf() to print the tracing info, since I'm happy to spend a few extra nanoseconds having more elegant code. So it could totally be improved further. Another thing is that right now it's only line buffered. So if it buffered between lines, it'd go faster.
I have a bpftrace script that measures template compile times across all compiler invocations on my machine, no recompilation required.
How do you figure? Call and ret should be just a couple of cycles each. And they run in parallel, so if you were waiting for a memory access or otherwise didn't have anything better to do, the overhead is even less.
Does anyone how this is meant?
They leverage Python's unit tests, running them on a cosmopolitan-libc linked Python version?
git clone https://github.com/jart/cosmopolitan.git
cd cosmopolitan
make -j16 o//third_party/python
That command will build Python and all its dependencies from scratch within the hermetic monorepo in addition to running its unit tests. On my $1,000 Core i9-9900 PC this takes 31.078 seconds.https://web.archive.org/web/20220520063319/https://justine.l...
One thing you can generally do to verify the authenticity of the binaries I publish, is go on VirusTotal and check to see if there's an upvote from "howishexeasier" since that's me. It's the closest thing to code signing that a multiplatform binary format allows, and honestly I think all platforms should use the service to check binaries.
What jart's does better than mine is that hers is not opt-in; you don't have to put special macros for it. Mine requires that. Hers also can instrument all functions; mine does not get external functions.
What mine does better is that it is not a security issue wrt W^X, and it also implements scope- and function-based RAII. For example, now that I have implemented it, I can change all of my direct calls to unlock mutexes at the end of a function into destructors, and change the lock calls to use the stackpools. After doing that, then any function that buys into the system I have, and that takes a lock, will release the lock by the end of the function without anything special, just like using RAII with locks in C++.
I can turn ftracing on and off at build time as well. [3]
Using the tests/gaml/gaml_fuzz program, which is still incomplete, you can see this. Assuming the build directory is `build/`, running:
cat ../build.gaml | tests/gaml/gaml_fuzz
with ftracing turned on will give you an ftrace that looks like: main()
y_fs_filebytes_fread()
y_strucon_handleError()
y_strucon_handleError()
y_fs_filebytes_fread()
y_strucon_status()
y_strucon_status()
y_exit()
I could make it prettier, and I probably will in subsequent commits.In other words, it's not magic, and in my opinion, it's not really a good idea because of the security implications because while you could argue that it's only for development, it's still another route to gain access to a developer's machine or a CI machine.
[1]: https://git.yzena.com/Yzena/Yc/src/commit/80713280c429850553...
[2]: https://git.yzena.com/Yzena/Yc/src/commit/80713280c429850553...
[3]: https://git.yzena.com/Yzena/Yc/src/commit/80713280c429850553...
Yes, JIT is insecure. You best hope that you don't have some kind of vulnerability in your JIT or an attacker can do return-oriented programming by making their own gadgets, i.e., they don't have to find the gadget they want, they can just create it.
In fact, this exact problem with JIT's is why I don't implement interpreters with JIT's. Instead, I generate bytecode and then run that because I can more easily sandbox it that way.
But that is ignoring the biggest elephant in the room. You're doing this in a libc. The libc is where attackers usually search for gadgets. It's not a good combination to have a libc, one of the highest value targets, to allow self-modifying code with an easy way to activate it.
I remember trying Cosmopolitan once. Ran into a bug right away. It couldn't handle spaces in a filename. I can only imagine what kinds of bugs it still has and continues to get as you add features. And what bugs of those will allow creation and exploitation of gadgets now that you have this?
We had a conversation on lobste.rs where I talked about why I don't think what you're doing is a good idea. You deleted everything you said. That's not a good look. The bug I ran into trying Cosmopolitan was not a good look. Not understanding the risks of what you are doing is not a good look.
All of that has made Cosmopolitan radioactive to me. I won't touch it.
Then show me the GitHub issue you filed that proves it. If you're talking about the Makefile configuration, I don't care.
> I can only imagine what kinds of bugs it still has and continues to get as you add features. [...] All of that has made Cosmopolitan radioactive to me. I won't touch it.
Good. Please stop following me.
You already fixed it [1], which was good. I left that experience with a neutral feeling; it was good that you fixed it, but it was not good that such a common thing was not handled. I decided to keep a watch on Cosmopolitan with a bit of happy anticipation.
(It was later seeing you on this site and lobste.rs that left a bad taste in my mouth.)
You're right, though, that a Makefile won't be able to handle it, and that that's not Cosmopolitan's problem, though I would suggest a different build system.
> Good. Please stop following me.
Oh, I don't follow you. But I check this site and lobste.rs regularly, and I see you post. Since I believe that Cosmopolitan is the wrong direction for the industry to go, I express that opinion. Nothing wrong with that, especially since you do the opposite by so zealously marketing Cosmopolitan. And it's easy to notice one of your submissions; your domain is instantly recognizable, as is your username.
Basically, Cosmopolitan may be radioactive to me, but that doesn't mean I won't express my opinion about it when I see a submission about it, and that does not mean I'm "following" you; it just means I'm an opportunist.
If expressing my opinion of your software makes you unhappy, that's not my problem, it is yours.