You can use /usr/bin/otool -tV
from the command-line to disassemble Mach-O files. If you don't know what Mach-O's are, they're like Krusty-O's only tastier and healthier. One problem with otool, however, is that it doesn't resolve rip-relative addresses. In the x86_64 (64-bit Intel) architecture, rip is the register that contains the instruction pointer. A rip-relative address is an address specified by an offset, either positive or negative, from the instruction pointer. For example, 0x99eac(%rip)
is an rip-relative address in this disassembly:
00000000000016ec cmpq $-0x1, 0x99eac(%rip)
00000000000016f4 jne 0x16fe
The instruction pointer actually points at the next instruction, so the rip-relative address resolves to 0x99eac + 0x16f4 = 0x9b5a0
. Unfortunately, otool doesn't print this value. It's tedious to manually resolve each rip-relative address, and it's impossible to search the otool output for a specific address if that address is rip-relative. Thus, otool is not as helpful as it could be.
Fortunately, I am as helpful as I could be! I've written a little command-line tool to parse the output from otool and resolve the rip-relative addresses. The source code is below. It's Objective-C, because that's how I roll. Disclaimer: my tool, which I call "riptool", only works with x86_64 files. Some people say that the Intel architecture is dying, but I think that the rumors of its death have been greatly exaggerated. Anyway, here's what the disassembly looks like from riptool:
00000000000016ec cmpq $-0x1, 0x99eac(%rip) [0x9b5a0]
00000000000016f4 jne 0x16fe
The source code is not released under any software license, because licenses are for lawyers, and IANAL. This blog post is for programmers. Here's the deal: if I like you, then I won't sue you for using the code. I probably like you, or at least don't dislike you. If you have lawyers, and your lawyers are worried that I dislike you, then contact me for a license. If the price is right, I can learn to like almost anybody.
// Copyright 2020 Jeff Johnson. All rights reserved.
#import <Foundation/Foundation.h>
int main(int argc, const char* argv[]) {
@autoreleasepool {
if (argc != 2) {
printf("Usage: %s <object file>\n", argv[0]);
return EXIT_FAILURE;
}
NSString* path = [NSString stringWithUTF8String:argv[1]];
if (path == nil) {
printf("invalid path: %s\n", argv[1]);
return EXIT_FAILURE;
}
NSTask* task = [[NSTask alloc] init];
[task setLaunchPath:@"/usr/bin/otool"];
[task setArguments:@[@"-tV", path]];
NSPipe* pipe = [NSPipe pipe];
[task setStandardOutput:pipe];
NSFileHandle* fileHandle = [pipe fileHandleForReading];
NSError* error = nil;
if (![task launchAndReturnError:&error]) {
NSLog(@"launch error: %@", error);
return EXIT_FAILURE;
}
NSData* data = [fileHandle readDataToEndOfFile];
if ([data length] == 0) {
NSLog(@"no output");
return EXIT_FAILURE;
}
NSString* string = [[NSString alloc] initWithData:data encoding:NSMacOSRomanStringEncoding];
if (string == nil) {
NSLog(@"not NSMacOSRomanStringEncoding: %@", data);
return EXIT_FAILURE;
}
__block unsigned long long offset = 0;
__block BOOL isNegative = NO;
__block NSMutableArray* previousLines = [NSMutableArray array];
[string enumerateLinesUsingBlock:^(NSString* line, BOOL* stop) {
if ([previousLines count] > 0) {
if ([line length] > 16) {
NSScanner* scanner = [NSScanner scannerWithString:line];
[scanner setCharactersToBeSkipped:nil];
unsigned long long address = 0;
if ([scanner scanHexLongLong:&address] && address > 0) {
if (isNegative) {
address -= offset;
isNegative = NO;
} else {
address += offset;
}
printf(" [0x%llx]", address);
for (NSString* previous in previousLines) {
printf("%s\n", [previous UTF8String]);
}
[previousLines removeAllObjects];
offset = 0;
} else {
[previousLines addObject:line];
return;
}
} else {
[previousLines addObject:line];
return;
}
}
NSRange ripRange = [line rangeOfString:@"(%rip)" options:0];
NSUInteger ripLocation = ripRange.location;
if (ripLocation != NSNotFound && ripLocation > (20 + 3)) {
NSRange hexRange = [line rangeOfString:@"0x" options:NSBackwardsSearch range:NSMakeRange(0, ripLocation)];
NSUInteger hexLocation = hexRange.location;
if (hexLocation != NSNotFound && hexLocation > 20) {
NSString* offsetString = [line substringWithRange:NSMakeRange(hexLocation, ripLocation - hexLocation)];
NSScanner* scanner = [NSScanner scannerWithString:offsetString];
[scanner setCharactersToBeSkipped:nil];
if ([scanner scanHexLongLong:&offset] && offset > 0) {
if ([[line substringWithRange:NSMakeRange(hexLocation - 1, 1)] isEqualToString:@"-"]) {
isNegative = YES;
}
NSUInteger splitIndex = ripLocation + ripRange.length;
NSString* prefix = [line substringToIndex:splitIndex];
NSString* suffix = [line substringFromIndex:splitIndex];
printf("%s", [prefix UTF8String]);
[previousLines addObject:suffix];
return;
}
}
}
printf("%s\n", [line UTF8String]);
}];
}
return EXIT_SUCCESS;
}