xrprof icon indicating copy to clipboard operation
xrprof copied to clipboard

Port to macOS

Open atheriel opened this issue 3 years ago • 5 comments

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() (see memory.h and memory.c) for macOS, likely using the vm_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() (see locate.h and locate.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() and proc_resume() (see process.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

atheriel avatar Jan 22 '21 16:01 atheriel

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)

jimhester avatar Jan 25 '21 15:01 jimhester

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.

atheriel avatar Jan 25 '21 18:01 atheriel

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.

atheriel avatar Jan 25 '21 18:01 atheriel

https://github.com/llvm/llvm-project/tree/main/libunwind is also probably useful for stack unwinding on macOS. It is what lldb uses.

jimhester avatar Jan 25 '21 18:01 jimhester

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.

atheriel avatar Jan 25 '21 18:01 atheriel