xrprof
xrprof copied to clipboard
Port to macOS
xrprof
is already somewhat designed with portability in mind. To get basic R profiling functionality working on macOS will require the following:
-
[ ] Implementing
copy_address()
(seememory.h
andmemory.c
) for macOS, likely using thevm_read()
function provided by that platform. For an idea of what this might look like, check out this well-known Rust implementation. -
[ ] Implementing
locate_libR_globals()
(seelocate.h
andlocate.c
) for macOS. I'm less sure of how to locate the address of a symbol on that platform, but it's definitely possible. -
[ ] Implementing
proc_suspend()
andproc_resume()
(seeprocess.h
) for macOS. Although not 100% necessary, sampling from a process when it's still running may yield incomplete/inconsistent stacks. -
[x] Modifying the build system as needed to compile on macOS.
Mixed R & C/C++ profiling will probably be more difficult to implement. I do not know if there is any "help" provided by macOS APIs for this, but obviously some tools (e.g. gdb
) are able to generate backtraces of a remote process on that platform.
cc @jimhester
Some notes after looking into this.
implementing copy_address()
seems quite straightforward, another straightforward implementation is in https://github.com/googleprojectzero/TinyInst/blob/3f39189be930e805d16c98555d3e3fc0a4b1d403/macOS/machtarget.cpp#L170-L219. I have a simple implementation locally that seems to work.
Locating the libR.dylib
used by a running process on macOS, and looking up the virtual addresses of symbols within that shared library does not seem as easy however. There is some code in that same project (https://github.com/googleprojectzero/TinyInst/blob/3f39189be930e805d16c98555d3e3fc0a4b1d403/macOS/debugger.cpp#L792) to do the symbol lookup from a shared library, however it requires you to load and parse the Mach-O headers yourself, there do not seem to be any public system or kernel interfaces to do this.
This code also would only work on intel based Macs, the new ARM Macs would likely need a completely different implementation.
Additionally any of these approaches would either need users to disable SIP for it to be usable, or have users go through a lengthy code signing process (e.g. https://sourceware.org/gdb/wiki/PermissionsDarwin)
I have a simple implementation locally that seems to work.
Feel free to submit that as a PR. Even if it's only partial support it's good to have it if it works at all.
it requires you to load and parse the Mach-O headers yourself, there do not seem to be any public system or kernel interfaces to do this.
I don't think this is hugely different from parsing the ELF format, which is what the Linux version does -- macOS does provide header files with all of the structures, just not this exact convenience function. Here is another implementation from the Austin project, for an additional reference: https://github.com/P403n1x87/austin/blob/master/src/mac/py_proc.h#L99. With those two implementations in hand it should be OK to try and write our own, or modify and import one of theirs.
Also, I'm not sure why you think that ARM-based Macs will be different -- I believe Mach-O binaries are laid out the same way (e.g. with the usual 64-bit sections). But we can detect the platform/CPU via the Mach-O header if needed and use that to perform additional logic.
Additionally any of these approaches would either need users to disable SIP for it to be usable, or have users go through a lengthy code signing process
Does it not work with sudo
? I have seen that there are issues profiling binaries in /bin
with SIP, but I didn't realize that that extended to turning it off entirely. It may also be possible to distribute signed binaries, though I'm not sure of that process.
But, some extremely good news about M1 macOS: according to this Apple spec, ARM requires the use of frame pointers. These are just about the most helpful thing in the world for unwinding a C/C++ stack, and probably means that mixed-mode profiling will be much easier to implement there than for x86.
https://github.com/llvm/llvm-project/tree/main/libunwind is also probably useful for stack unwinding on macOS. It is what lldb uses.
The (incomplete) remote unwinding support was removed from LLVM's libunwind
on macOS: https://reviews.llvm.org/D57252 Otherwise all of the existing unwinding code would "just work" on macOS. Sad.