initial checkin of Cocoa version of macdependency

This commit is contained in:
konrad_w 2009-09-17 13:06:04 +00:00
commit c733d07131
73 changed files with 3945 additions and 0 deletions

View File

@ -0,0 +1,36 @@
//
// ArchitectureModel.h
// MacDependency
//
// Created by Konrad Windszus on 22.08.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "MachOModel.h"
#include "macho/machoarchitecture.h"
#include "MachO/machoheader.h"
@class MyDocument;
@interface ArchitectureModel : NSObject {
MachOArchitecture* architecture;
MachOModel* machOModel;
MachO* file;
MyDocument* document;
NSMutableArray* symbolEntries;
}
- (id) initWithArchitecture:(MachOArchitecture*)architecture file:(MachO*)file document:(MyDocument*)document isRoot:(BOOL)isRoot;
- (NSString*) label;
- (NSString*) fileType;
- (NSString*) idName;
- (NSString*) size;
- (NSString*) version;
+ (NSString*) cpuType:(MachOHeader::CpuType)cpuType;
- (void) refreshSymbols;
- (NSArray*) symbols;
- (void) setSymbols:(NSMutableArray*) symbolEntries;
@end

View File

@ -0,0 +1,225 @@
//
// ArchitectureModel.m
// MacDependency
//
// Created by Konrad Windszus on 22.08.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
#import "ArchitectureModel.h"
#import "VersionFormatter.h"
#import "ConversionStdString.h"
#include "MachO/machoexception.h"
#include "MachO/symboltablecommand.h"
#import "SymbolTableEntryModel.h"
#import "MyDocument.h"
// declare private methods
@interface ArchitectureModel ()
- (void) initSymbols;
@end
@implementation ArchitectureModel
- (id) initWithArchitecture:(MachOArchitecture*)architecture file:(MachO*)file document:(MyDocument*)document isRoot:(BOOL)isRoot{
self->architecture = architecture;
self->file = file;
self->document = document;
self->symbolEntries = nil;
// create model with the current architecture selected (create from existing model)
if (isRoot) {
machOModel = nil;
} else {
machOModel = [[MachOModel alloc]initWithFile:file document:document architecture:architecture loadChildren:NO];
}
return self;
}
- (void)dealloc {
[symbolEntries release];
[machOModel release];
[super dealloc];
}
- (NSArray*) rootModel {
// always reload model (for error messages)
[machOModel release];
machOModel = [[MachOModel alloc]initWithFile:file document:document architecture:architecture loadChildren:YES];
NSArray* rootModel = [NSArray arrayWithObject:machOModel];
return rootModel;
}
+ (NSString*) cpuType:(MachOHeader::CpuType)cpuType {
NSString* label;
switch (cpuType) {
case MachOHeader::CpuTypePowerPc:
label = NSLocalizedString(@"CPU_TYPE_PPC", nil);
break;
case MachOHeader::CpuTypeI386:
label = NSLocalizedString(@"CPU_TYPE_I386", nil);
break;
case MachOHeader::CpuTypePowerPc64:
label = NSLocalizedString(@"CPU_TYPE_PPC64", nil);
break;
case MachOHeader::CpuTypeX8664:
label = NSLocalizedString(@"CPU_TYPE_X8664", nil);
break;
case MachOHeader::CpuTypeOther:
label = NSLocalizedString(@"UNDEFINED", nil);
break;
}
return label;
}
- (NSString*) label {
return [ArchitectureModel cpuType:architecture->getHeader()->getCpuType()];
}
- (NSString*) fileType {
NSString* type;
switch(architecture->getHeader()->getFileType()) {
case MachOHeader::FileTypeObject: /* relocatable object file */
type = NSLocalizedString(@"FILE_TYPE_OBJECT", nil);
break;
case MachOHeader::FileTypeExecutable: /* demand paged executable file */
type = NSLocalizedString(@"FILE_TYPE_EXECUTABLE", nil);
break;
case MachOHeader::FileTypeVmLib: /* fixed VM shared library file */
type = NSLocalizedString(@"FILE_TYPE_VM_SHARED_LIBRARY", nil);
break;
case MachOHeader::FileTypeCore: /* core file */
type = NSLocalizedString(@"FILE_TYPE_CORE", nil);
break;
case MachOHeader::FileTypePreload: /* preloaded executable file */
type = NSLocalizedString(@"FILE_TYPE_PRELOADED_EXECUTABLE", nil);
break;
case MachOHeader::FileTypeDylib: /* dynamically bound shared library */
type = NSLocalizedString(@"FILE_TYPE_SHARED_LIBRARY", nil);
break;
case MachOHeader::FileTypeDylinker: /* dynamic link editor */
type = NSLocalizedString(@"FILE_TYPE_DYNAMIC_LINKER", nil);
break;
case MachOHeader::FileTypeBundle: /* dynamically bound bundle file */
type = NSLocalizedString(@"FILE_TYPE_BUNDLE", nil);
break;
case MachOHeader::FileTypeDylibStub: /* shared library stub for static linking only, no section contents */
type = NSLocalizedString(@"FILE_TYPE_STATIC_LIBRARY", nil);
break;
case MachOHeader::FileTypeDsym: /* companion file with only debug sections */
type = NSLocalizedString(@"FILE_TYPE_DSYM", nil);
break;
default:
type = NSLocalizedString(@"UNDEFINED", @"Unknown");
}
return type;
}
- (NSString*) idName {
NSString* idName = [NSString string];
DylibCommand* dynamicLibraryIdCommand = architecture->getDynamicLibIdCommand();
if (dynamicLibraryIdCommand != NULL) {
idName = [NSString stringWithStdString:dynamicLibraryIdCommand->getName()];
}
return idName;
}
- (NSString*) size {
NSString* size;
// somehow we can't use this directly in stringWithFormat, because then the second parameter is always zero.
unsigned int architectureSize = architecture->getSize();
unsigned int fileSize = [machOModel size];
if (fileSize != architectureSize) {
size = [NSString stringWithFormat:NSLocalizedString(@"SIZE_FORMAT_ARCHITECTURE", nil),
fileSize/1024, architectureSize/1024];
} else {
size = [NSString stringWithFormat:NSLocalizedString(@"SIZE_FORMAT", nil),
fileSize/1024];
}
return size;
}
- (NSString*) version {
NSString* version = @"?";
DylibCommand* dynamicLibraryIdCommand = architecture->getDynamicLibIdCommand();
if (dynamicLibraryIdCommand != NULL) {
VersionFormatter* versionFormatter = [[VersionFormatter alloc] init];
time_t timestamp = dynamicLibraryIdCommand->getTimeStamp();
NSString* currentVersion = [versionFormatter stringForObjectValue:[NSNumber numberWithUnsignedInt:dynamicLibraryIdCommand->getCurrentVersion()]];
NSString* compatibleVersion = [versionFormatter stringForObjectValue:[NSNumber numberWithUnsignedInt:dynamicLibraryIdCommand->getCompatibleVersion()]];
if ([currentVersion length]+[compatibleVersion length] > 0) {
if (timestamp > 1) {
NSDate* date = [NSDate dateWithTimeIntervalSince1970:timestamp];
// this date formatter should be identical to NSDateFormatter in IB
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
version = [NSString stringWithFormat:NSLocalizedString(@"VERSION_FORMAT_TIMESTAMP", nil),
currentVersion,
compatibleVersion,
[dateFormatter stringFromDate:date]];
}
else {
version = [NSString stringWithFormat:NSLocalizedString(@"VERSION_FORMAT", nil),
currentVersion,
compatibleVersion];
}
}
[versionFormatter release];
} else {
version = [machOModel version];
}
return version;
}
- (void) initSymbols {
symbolEntries = [NSMutableArray arrayWithCapacity:20];
[symbolEntries retain];
for (MachOArchitecture::LoadCommandsConstIterator it = architecture->getLoadCommandsBegin();
it != architecture->getLoadCommandsEnd();
++it)
{
// check if it is dylibcommand
SymbolTableCommand* command = dynamic_cast<SymbolTableCommand*> (*it);
if (command != 0) {
for (SymbolTableCommand::SymbolTableEntriesConstIterator it = command->getSymbolTableEntryBegin(); it != command->getSymbolTableEntryEnd(); it++) {
SymbolTableEntryModel* symbolModel = [[SymbolTableEntryModel alloc] initWithEntry:*(it) demangleNamesPtr:[[document symbolTableController]demangleNamesPtr]];
[symbolEntries addObject:symbolModel];
}
}
}
}
- (NSArray*) symbols {
if (symbolEntries == nil) {
[self initSymbols];
}
return symbolEntries;
}
- (void) setSymbols:(NSMutableArray*) symbolEntries {
[symbolEntries retain];
[self->symbolEntries release];
self->symbolEntries = symbolEntries;
}
- (void) refreshSymbols {
[symbolEntries release];
[self initSymbols];
[self setSymbols:symbolEntries];
}
@end

View File

@ -0,0 +1,16 @@
//
// ArchitecturesController.h
// MacDependency
//
// Created by Konrad Windszus on 22.08.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class MyDocument;
@interface ArchitecturesController : NSArrayController {
IBOutlet MyDocument* document;
}
@end

View File

@ -0,0 +1,47 @@
//
// ArchitecturesController.m
// MacDependency
//
// Created by Konrad Windszus on 22.08.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
// Only used for root architectures.
//
#import "ArchitecturesController.h"
#import "MyDocument.h"
// declare private methods
@interface ArchitecturesController ()
@end
@implementation ArchitecturesController
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (self) {
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (void)awakeFromNib
{
[super awakeFromNib];
}
- (void)setSelectionIndex:(NSUInteger)index {
// clear log
[document clearLog];
[document resetNumDependencies];
[super setSelectionIndex:index];
}
@end

View File

@ -0,0 +1,16 @@
//
// AutoExpandOutlineView.h
// MacDependency
//
// Created by Konrad Windszus on 03.09.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface AutoExpandOutlineView : NSOutlineView {
}
@end

View File

@ -0,0 +1,26 @@
//
// AutoExpandOutlineView.m
// MacDependency
//
// Created by Konrad Windszus on 03.09.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
#import "AutoExpandOutlineView.h"
@implementation AutoExpandOutlineView
// gets called after binding has changed
- (void)reloadData;
{
[super reloadData];
// auto expand root item
NSTreeNode* item = [self itemAtRow:0];
if (item)
[self expandItem:item];
}
@end

View File

@ -0,0 +1,19 @@
//
// ConversionStdString.h
// MacDependency
//
// Created by Konrad Windszus on 17.07.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#include <string>
@interface NSString (ConversionStdString)
+ (NSString*) stringWithStdString:(const std::string&)string;
- (std::string) stdString;
+ (NSString*) stringWithStdWString:(const std::wstring&)string;
- (std::wstring) stdWString;
@end

View File

@ -0,0 +1,46 @@
//
// ConversionStdString.m
// MacDependency
//
// Created by Konrad Windszus on 17.07.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
// Conversion functions for NSString from/to std::string and std::wstring
#import "ConversionStdString.h"
@implementation NSString (ConversionStdString)
+ (NSString*) stringWithStdString:(const std::string&)string {
return [NSString stringWithCString:string.c_str() encoding:NSASCIIStringEncoding];
}
- (std::string) stdString {
return std::string([self cStringUsingEncoding:NSASCIIStringEncoding]);
}
#if TARGET_RT_BIG_ENDIAN
const NSStringEncoding kEncoding_wchar_t =
CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingUTF32BE);
#else
const NSStringEncoding kEncoding_wchar_t =
CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingUTF32LE);
#endif
+(NSString*) stringWithStdWString:(const std::wstring&)ws
{
char* data = (char*)ws.data();
unsigned size = ws.size() * sizeof(wchar_t);
NSString* result = [[[NSString alloc] initWithBytes:data length:size
encoding:kEncoding_wchar_t] autorelease];
return result;
}
-(std::wstring) stdWString
{
NSData* asData = [self dataUsingEncoding:kEncoding_wchar_t];
return std::wstring((wchar_t*)[asData bytes], [asData length] /
sizeof(wchar_t));
}
@end

View File

@ -0,0 +1,14 @@
//
// ExtTreeModel.h
// MacDependency
//
// Created by Konrad Windszus on 02.09.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
// Protocol which provides a method to get the parent (used by TreeControllerExtension)
#import <Cocoa/Cocoa.h>
@protocol ExtTreeModel
- (id) parent;
@end

47
MacDependency/Info.plist Normal file
View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>app</string>
<string>txt</string>
<string>*</string>
</array>
<key>CFBundleTypeIconFile</key>
<string></string>
<key>CFBundleTypeName</key>
<string>DocumentType</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>NSDocumentClass</key>
<string>MyDocument</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.googlecode.${PRODUCT_NAME:identifier}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@ -0,0 +1,7 @@
//
// Prefix header for all source files of the 'MacDependency' target in the 'MacDependency' project
//
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#endif

View File

@ -0,0 +1,52 @@
//
// MachOModel.h
// MacDependency
//
// Created by Konrad Windszus on 13.07.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "ExtTreeModel.h"
#import "MachO/machoarchitecture.h"
#import "MachO/dylibcommand.h"
#import "MachO/macho.h"
#import "ArchitecturesController.h"
@class MyDocument;
typedef enum State{
StateNormal,
StateWarning,
StateError
};
@interface MachOModel : NSObject <ExtTreeModel> {
NSMutableArray* children;
MachO* file;
MyDocument* document;
MachOModel* parent;
MachOArchitecture* architecture; // current architecture
DylibCommand* command;
State state;
}
- (id) initWithFile:(MachO*)machO document:(MyDocument*)document architecture:(MachOArchitecture*)architecture loadChildren:(BOOL)loadChildren;
- (id) initWithFilename:(std::string&)filename command:(DylibCommand*)command document:(MyDocument*)document parent:(MachOModel*)parent;
- (NSArray*) children;
- (BOOL) isLeaf;
- (NSColor*) textColor;
- (NSString*) name;
- (NSNumber*) currentVersion;
- (NSNumber*) compatibleVersion;
- (NSString*) filename;
- (NSString*) dependencyType;
- (NSString*) version;
- (NSString*) idName;
- (NSDate*) lastModificationTime;
- (NSArray*) architectures;
- (unsigned int) size;
@end

327
MacDependency/MachOModel.mm Normal file
View File

@ -0,0 +1,327 @@
//
// MachOModel.m
// MacDependency
//
// Created by Konrad Windszus on 13.07.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
#import "MachOModel.h"
#import "MyDocument.h"
#import "ConversionStdString.h"
#import "VersionFormatter.h"
#import "ArchitectureModel.h"
#include "MachO/machoexception.h"
#include "MachO/machoheader.h"
#include "MachO/symboltablecommand.h"
// declare private methods
@interface MachOModel ()
- (void) initChildren;
- (void) checkConstraints:(DylibCommand*) dylibId;
- (void) setStateWithWarning:(BOOL)isWarning;
- (id) initWithFile:(MachO*)file command:(DylibCommand*)command document:(MyDocument*)document parent:(MachOModel*)parent architecture:(MachOArchitecture*)architecture;
@end
@implementation MachOModel
- (id) initWithFile:(MachO*)machO document:(MyDocument*)document architecture:(MachOArchitecture*)architecture loadChildren:(BOOL)loadChildren {
self->state = StateNormal;
[self initWithFile:machO command:nil document:document parent:nil architecture:architecture];
if (loadChildren) {
[self initChildren];
}
return self;
}
// called by initChildren
- (id) initWithFilename:(std::string&)filename command:(DylibCommand*)command document:(MyDocument*)document parent:(MachOModel*)parent {
BOOL isWeakReference = (command && !command->isNecessary());
self->state = StateNormal;
try {
file = [document cache]->getFile(filename, parent->file); // throws exception in case file is not found
architecture = file->getCompatibleArchitecture(parent->architecture);
if (!architecture) {
[self setStateWithWarning:isWeakReference];
NSString* log = [NSString stringWithFormat:NSLocalizedString(@"ERR_ARCHITECTURE_MISMATCH", nil), parent->command->getName().c_str(), [parent name]];
[document appendLogLine:log withModel:self state:state];
}
} catch (MachOException& exc) {
[self setStateWithWarning:isWeakReference];
NSString* log = [NSString stringWithStdString:exc.getCause()];
[document appendLogLine:log withModel:self state:state];
// distinguish between weak and strong. In both cases append to tree with a status color
}
[document incrementNumDependencies];
[self initWithFile:file command:command document:document parent:parent architecture:architecture];
return self;
}
// private
- (id) initWithFile:(MachO*)file command:(DylibCommand*)command document:(MyDocument*)document parent:(MachOModel*)parent architecture:(MachOArchitecture*)architecture {
if (self = [super init]) {
self->document = document;
self->parent = parent;
self->command = command;
self->file = file;
self->children = nil;
self->architecture = architecture;
BOOL isWeakReference = (command && !command->isNecessary());
if (architecture) {
DylibCommand* dylibId = architecture->getDynamicLibIdCommand();
if (dylibId && command) {
[self checkConstraints:dylibId];
}
}
} return self;
}
- (void)dealloc {
[children release];
[super dealloc];
}
- (void) setStateWithWarning:(BOOL)isWarning {
State state = StateError;
if (isWarning) {
state = StateWarning;
}
if (self->state < state) {
self->state = state;
}
}
- (id) parent {
return parent;
}
- (void) checkConstraints:(DylibCommand*) dylibId {
// check version information (only compatible version information is relevant)
unsigned int minVersion = dylibId->getCompatibleVersion();
unsigned int requestedMinVersion = command->getCompatibleVersion();
unsigned int requestedMaxVersion = command->getCurrentVersion();
VersionFormatter* versionFormatter = [[VersionFormatter alloc] init];
BOOL isWeakReference = (command && !command->isNecessary());
NSString* log;
// check minimum version
if (minVersion != 0 && requestedMinVersion != 0 && minVersion < requestedMinVersion) {
[self setStateWithWarning:isWeakReference];
log = [NSString stringWithFormat:NSLocalizedString(@"ERR_MINIMUM_VERSION", nil), parent->command->getName().c_str(), [parent name], [versionFormatter stringForObjectValue:[NSNumber numberWithUnsignedInt:requestedMinVersion]], [versionFormatter stringForObjectValue:[NSNumber numberWithUnsignedInt:minVersion]]];
[document appendLogLine:log withModel:self state:state];
}
// extended checks which are currently not done by dyld
// check maximum version
if (minVersion != 0 && requestedMaxVersion != 0 && minVersion > requestedMaxVersion) {
[self setStateWithWarning:YES];
log = [NSString stringWithFormat:NSLocalizedString(@"ERR_MAXIMUM_VERSION", nil), command->getName().c_str(), [parent name], [versionFormatter stringForObjectValue:[NSNumber numberWithUnsignedInt:requestedMaxVersion]], [versionFormatter stringForObjectValue:[NSNumber numberWithUnsignedInt:minVersion]]];
[document appendLogLine:log withModel:self state:state];
}
// check names
if (dylibId->getName() != command->getName()) {
[self setStateWithWarning:YES];
log = [NSString stringWithFormat:NSLocalizedString(@"ERR_NAME_MISMATCH", nil), command->getName().c_str(), [parent name], dylibId->getName().c_str()];
[document appendLogLine:log withModel:self state:state];
}
[versionFormatter release];
}
- (NSIndexPath*) calculateIndexPath {
unsigned int depth = 0;
// determine current depth
MachOModel* model = self;
while (model = model->parent) {
depth++;
}
unsigned int length = depth+1;
NSUInteger* indices = new NSUInteger[length];
MachOModel* child;
// search me in parent (recursively)
model = child = self;
while (model = model->parent) {
// go through children
NSEnumerator *enumerator = [[model children] objectEnumerator];
NSUInteger index = 0;
while (child != [enumerator nextObject]) {
index++;
}
indices[depth--] = index;
child = model;
}
indices[0] = 0;
return [NSIndexPath indexPathWithIndexes:indices length:length];
}
- (NSArray*)children {
if (children == nil) {
[self initChildren];
}
return children;
}
- (void) initChildren {
// TODO: tweak capacity
children = [NSMutableArray arrayWithCapacity:20];
[children retain];
if (architecture) {
for (MachOArchitecture::LoadCommandsConstIterator it = architecture->getLoadCommandsBegin();
it != architecture->getLoadCommandsEnd();
++it) {
LoadCommand* childLoadCommand = (*it);
// check if it is dylibcommand
DylibCommand* dylibCommand = dynamic_cast<DylibCommand*> (childLoadCommand);
if (dylibCommand != NULL && !dylibCommand->isId()) {
// TODO: check RPath handling, must use root for working path
std::string filename = architecture->getResolvedName(dylibCommand->getName(), "");
MachOModel* child = [MachOModel alloc];
[children addObject:child]; // must add children before initializing them, because in init we rely on parent children being correct
[child initWithFilename:filename command:dylibCommand document:document parent:self];
[child release];
}
}
}
}
- (BOOL)isLeaf {
[self children];
if ([children count] == 0)
return YES;
return NO;
}
- (NSColor*) textColor {
NSColor* color;
switch(state) {
case StateWarning:
color = [NSColor blueColor];
break;
case StateError:
color = [NSColor redColor];
break;
default:
color= [NSColor blackColor];
}
return color;
}
- (NSString*) filename {
std::string filename;
if (file)
filename = file->getFileName();
return [NSString stringWithStdString:filename];
}
- (NSString*) version {
if (file) {
return [NSString stringWithStdString:file->getVersion()];
}
return @"";
}
- (unsigned int) size {
if (file)
return file->getSize();
return 0;
}
- (NSString*) name {
std::string name;
if (command) {
name = command->getName();
} else if (file) {
name = file->getName();
}
return [NSString stringWithStdString:name];
}
- (NSNumber*) currentVersion {
if (command) {
return [NSNumber numberWithUnsignedInt:command->getCurrentVersion()];
}
return [NSNumber numberWithUnsignedInt:0];
}
- (NSNumber*) compatibleVersion {
if (command) {
return [NSNumber numberWithUnsignedInt:command->getCompatibleVersion()];
}
return [NSNumber numberWithUnsignedInt:0];
}
- (NSDate*) lastModificationTime {
if (file)
return [NSDate dateWithTimeIntervalSince1970:file->getLastModificationTime()];
return NULL;
}
- (NSString*) dependencyType {
NSString* type;
if (!command) {
return @"";
}
switch(command->getType()) {
case DylibCommand::DependencyWeak:
type = NSLocalizedString(@"DEPENDENCY_TYPE_WEAK", nil);
break;
case DylibCommand::DependencyDelayed:
type = NSLocalizedString(@"DEPENDENCY_TYPE_DELAYED", nil);
break;
case DylibCommand::DependencyNormal:
type = NSLocalizedString(@"DEPENDENCY_TYPE_NORMAL", nil);
break;
default:
type = NSLocalizedString(@"UNDEFINED", @"Unknown");
}
return type;
}
- (NSArray*) architectures {
NSMutableArray* architectures = [NSMutableArray arrayWithCapacity:4];
if (file) {
for (MachO::MachOArchitecturesIterator iter = file->getArchitecturesBegin(); iter != file->getArchitecturesEnd(); iter++) {
// create model for architecture
ArchitectureModel* currentArchitecture = [[ArchitectureModel alloc]initWithArchitecture:(*iter) file:file document:document isRoot:NO];
// correct order (current architecture should have first index)
if ((*iter) == architecture) {
[architectures insertObject:currentArchitecture atIndex:0]; // insert at beginning
} else {
[architectures addObject:currentArchitecture]; // insert at end
}
[currentArchitecture release];
}
}
return architectures;
}
- (NSString*) idName {
// TODO: what should we return here?
return @"";
}
@end

View File

@ -0,0 +1,44 @@
//
// MyDocument.h
// MacDependency
//
// Created by Konrad Windszus on 19.06.09.
// Copyright Konrad Windszus 2009 . All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "MachO/macho.h"
#import "MachO/machocache.h"
#import "MachOModel.h"
#import "PrioritySplitViewDelegate.h"
#import "SymbolTableController.h"
@interface MyDocument : NSDocument
{
MachOCache* cache;
MachO* machO;
NSArray* contents;
NSMutableAttributedString* log;
PrioritySplitViewDelegate* splitViewDelegate;
IBOutlet NSTreeController* dependenciesController;
IBOutlet NSTextField* textFieldFilename;
IBOutlet NSTextField* textFieldBottomBar;
IBOutlet NSSplitView* mainSplitView;
IBOutlet SymbolTableController* symbolTableController;
unsigned int numDependencies;
}
- (NSAttributedString*)log;
- (void)setLog:(NSMutableAttributedString *)newLog;
- (void)appendLogLine:(NSString *)line withModel:(MachOModel*)model state:(State)state;
- (void)clearLog;
- (void)incrementNumDependencies;
- (void)resetNumDependencies;
- (MachOCache*)cache;
- (NSArray*)architectures;
- (IBAction)clickRevealInFinder:(id)sender;
- (NSString*)dependencyStatus;
- (SymbolTableController*)symbolTableController;
@end

286
MacDependency/MyDocument.mm Normal file
View File

@ -0,0 +1,286 @@
//
// MyDocument.m
// MacDependency
//
// Created by Konrad Windszus on 19.06.09.
// Copyright Konrad Windszus 2009 . All rights reserved.
//
#import "MyDocument.h"
#import "ConversionStdString.h"
#import "MachOModel.h"
#import "TreeControllerExtension.h"
#import "ArchitectureModel.h"
#include "MachO/machoexception.h"
// declare private methods
@interface MyDocument ()
- (NSString*) serializeIndexPath:(NSIndexPath*)indexPath;
- (NSIndexPath*) deserializeIndexPath:(NSString*)link;
@end
@implementation MyDocument
- (id)init
{
self = [super init];
if (self) {
// Add your subclass-specific initialization here.
// If an error occurs here, send a [self release] message and return nil.
//contents = [[NSMutableArray alloc] init];
cache = new MachOCache();
log = [[NSAttributedString alloc] initWithString:@""];
numDependencies = 0;
splitViewDelegate = [[PrioritySplitViewDelegate alloc] init];
}
return self;
}
- (void) dealloc
{
delete cache;
[splitViewDelegate release];
[contents release];
[log release];
[super dealloc];
}
- (NSString *)windowNibName
{
// Override returning the nib file name of the document
// If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
return @"MyDocument";
}
- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
[super windowControllerDidLoadNib:aController];
// Add any code here that needs to be executed once the windowController has loaded the document's window.
}
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
// Insert code here to write your document to data of the specified type. If the given outError != NULL, ensure that you set *outError when returning nil.
// You can also choose to override -fileWrapperOfType:error:, -writeToURL:ofType:error:, or -writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead.
// For applications targeted for Panther or earlier systems, you should use the deprecated API -dataRepresentationOfType:. In this case you can also choose to override -fileWrapperRepresentationOfType: or -writeToFile:ofType: instead.
if ( outError != NULL ) {
*outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:NULL];
}
return nil;
}
- (BOOL)readFromURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError
{
/*if ( outError != NULL ) {
//An error occurred
NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
[errorDetail setValue:@"Failed to do something wicked" forKey:NSLocalizedDescriptionKey];
*outError = [NSError errorWithDomain:@"myDomain" code:100 userInfo:errorDetail];
//*outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:dskFulErr userInfo:nil];
return NO;
}*/
//return YES;
// load file
NSString* file = [super fileName];
// convert to std:string
std::string fileString = [file stdString];
try {
machO = cache->getFile(fileString, NULL);
} catch (MachOException& exc) {
NSString* msg = [NSString stringWithUTF8String:exc.getCause().c_str()];
// Make and return custom domain error
NSArray *objArray = [NSArray arrayWithObjects:@"hello", msg, @"Try again with another file", nil];
NSArray *keyArray = [NSArray arrayWithObjects:NSLocalizedDescriptionKey, NSLocalizedFailureReasonErrorKey, NSLocalizedRecoverySuggestionErrorKey, nil];
NSDictionary *eDict = [NSDictionary dictionaryWithObjects:objArray forKeys:keyArray];
// fill outError
*outError = [NSError errorWithDomain:@"MachO" code:0 userInfo:eDict];
//NSAlert* alert = [NSAlert alertWithMessageText:msg defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:nil];
//[alert runModal];
return NO;
}
return YES;
}
-(void)awakeFromNib {
[[textFieldBottomBar cell] setBackgroundStyle:NSBackgroundStyleRaised];
[splitViewDelegate setPriority:1 forViewAtIndex:0];
[splitViewDelegate setPriority:0 forViewAtIndex:1];
[splitViewDelegate setMinimumLength:150 forViewAtIndex:0];
[splitViewDelegate setMinimumLength:400 forViewAtIndex:1];
[mainSplitView setDelegate:splitViewDelegate];
}
- (NSAttributedString*)log {
return log;
}
- (void)setLog:(NSMutableAttributedString *)newLog {
[newLog retain];
[log release];
log = newLog;
}
- (void)clearLog {
NSAttributedString* newLog = [[NSAttributedString alloc] initWithString:@""];
[self setLog:newLog];
[newLog release];
}
- (void)appendLogLine:(NSString *)line withModel:(MachOModel*)model state:(State)state {
NSMutableAttributedString* newLog = [[NSMutableAttributedString alloc] init];
[newLog appendAttributedString:log];
NSString* prefix;
switch (state) {
case StateError:
prefix = NSLocalizedString(@"LOG_PREFIX_ERROR", nil);
break;
default:
prefix = NSLocalizedString(@"LOG_PREFIX_WARNING", nil);
break;
}
NSString* newLine = [NSString stringWithFormat:@"%@%@\n\n", prefix, line];
// new line consist of prefix and link
//NSString* link = [self serializeIndexPath:indexPath];
NSDictionary* attributes;
if (model) {
attributes = [NSDictionary dictionaryWithObjectsAndKeys:model, NSLinkAttributeName, [NSCursor pointingHandCursor], NSCursorAttributeName, NSLocalizedString(@"LOG_LINK_TOOLTIP", nil), NSToolTipAttributeName, nil];
} else {
attributes = [NSDictionary dictionary];
}
NSAttributedString* newLogLine = [[NSAttributedString alloc]initWithString:newLine attributes:attributes];
[newLog appendAttributedString:newLogLine];
[newLogLine release];
[self setLog:newLog];
[newLog release];
}
- (NSString*) serializeIndexPath:(NSIndexPath*)indexPath {
NSMutableString* link = [NSMutableString stringWithCapacity:20];
for (int depth = 0; depth < [indexPath length]; depth++) {
[link appendFormat:@"%d;", [indexPath indexAtPosition:depth]];
}
return link;
}
- (NSIndexPath*) deserializeIndexPath:(NSString*)link {
NSIndexPath* indexPath;
// tokenize string
NSArray* indices = [link componentsSeparatedByString:@";"];
// go through tokens
NSEnumerator *enumerator = [indices objectEnumerator];
NSString* token = [enumerator nextObject];
if (token) {
indexPath = [NSIndexPath indexPathWithIndex:[token intValue]];
while (token = [enumerator nextObject]) {
if ([token length] > 0)
indexPath = [indexPath indexPathByAddingIndex:[token intValue]];
}
}
return indexPath;
}
// delegate method
- (BOOL)textView:(NSTextView *)aTextView clickedOnLink:(id)link atIndex:(NSUInteger)charIndex {
// link must be NSString which should be deserialized here
//NSIndexPath* indexPath = [self deserializeIndexPath:link];
[dependenciesController setSelectedObject:link];
// we need no further processing of the link
return YES;
}
- (MachOCache*)cache {
return cache;
}
- (NSArray*) architectures {
NSMutableArray* architectures = [NSMutableArray arrayWithCapacity:4];
if (machO) {
MachOArchitecture* architecture = machO->getHostCompatibleArchitecture();
if (!architecture) {
// no host-compatible architecture found (just take first architecture)
architecture = *(machO->getArchitecturesBegin());
}
for (MachO::MachOArchitecturesIterator iter = machO->getArchitecturesBegin(); iter != machO->getArchitecturesEnd(); iter++) {
// create model for architecture
ArchitectureModel* currentArchitecture = [[ArchitectureModel alloc]initWithArchitecture:(*iter) file:machO document:self isRoot:YES];
// correct order (current architecture should have first index)
if ((*iter) == architecture) {
[architectures insertObject:currentArchitecture atIndex:0]; // insert at beginning
} else {
[architectures addObject:currentArchitecture]; // insert at end
}
[currentArchitecture release];
}
}
return architectures;
}
- (IBAction)clickRevealInFinder:(id)sender {
NSString* filename = [textFieldFilename stringValue];
[[NSWorkspace sharedWorkspace] selectFile:filename
inFileViewerRootedAtPath:nil];
}
- (void)incrementNumDependencies {
[self willChangeValueForKey:@"dependencyStatus"];
numDependencies++;
[self didChangeValueForKey:@"dependencyStatus"];
}
- (void)resetNumDependencies {
[self willChangeValueForKey:@"dependencyStatus"];
numDependencies = 0;
[self didChangeValueForKey:@"dependencyStatus"];
}
- (NSString*)dependencyStatus {
NSString* status = [NSString stringWithFormat:NSLocalizedString(@"DEPENDENCY_STATUS", nil), numDependencies, cache->getNumEntries()];
return status;
}
- (SymbolTableController*)symbolTableController {
return symbolTableController;
}
@end

View File

@ -0,0 +1,16 @@
//
// MyDocumentWindow.h
// MacDependency
//
// Created by Konrad Windszus on 04.09.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface MyDocumentWindow : NSWindow {
}
@end

View File

@ -0,0 +1,21 @@
//
// MyDocumentWindow.m
// MacDependency
//
// Created by Konrad Windszus on 04.09.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
// Window class for document windows
//
#import "MyDocumentWindow.h"
@implementation MyDocumentWindow
-(void)awakeFromNib {
[super awakeFromNib];
// set bottom bar
[self setContentBorderThickness:24.0 forEdge:NSMinYEdge];
}
@end

View File

@ -0,0 +1,22 @@
//
// PrioritySplitViewDelegate.h
// ColumnSplitView
//
// Created by Matt Gallagher on 2009/09/01.
// Copyright 2009 Matt Gallagher. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface PrioritySplitViewDelegate : NSObject
{
NSMutableDictionary *lengthsByViewIndex;
NSMutableDictionary *viewIndicesByPriority;
}
- (void)setMinimumLength:(CGFloat)minLength
forViewAtIndex:(NSInteger)viewIndex;
- (void)setPriority:(NSInteger)priorityIndex
forViewAtIndex:(NSInteger)viewIndex;
@end

View File

@ -0,0 +1,180 @@
//
// PrioritySplitViewDelegate.m
// ColumnSplitView
//
// Created by Matt Gallagher on 2009/09/01.
// Copyright 2009 Matt Gallagher. All rights reserved.
//
// see http://cocoawithlove.com/2009/09/nssplitview-delegate-for-priority-based.html
//
#import "PrioritySplitViewDelegate.h"
@implementation PrioritySplitViewDelegate
- (void)setMinimumLength:(CGFloat)minLength forViewAtIndex:(NSInteger)viewIndex
{
if (!lengthsByViewIndex)
{
lengthsByViewIndex = [[NSMutableDictionary alloc] initWithCapacity:0];
}
[lengthsByViewIndex
setObject:[NSNumber numberWithDouble:minLength]
forKey:[NSNumber numberWithInteger:viewIndex]];
}
- (void)setPriority:(NSInteger)priorityIndex forViewAtIndex:(NSInteger)viewIndex
{
if (!viewIndicesByPriority)
{
viewIndicesByPriority = [[NSMutableDictionary alloc] initWithCapacity:0];
}
[viewIndicesByPriority
setObject:[NSNumber numberWithInteger:viewIndex]
forKey:[NSNumber numberWithInteger:priorityIndex]];
}
- (CGFloat)splitView:(NSSplitView *)sender
constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset
{
NSView *subview = [[sender subviews] objectAtIndex:offset];
NSRect subviewFrame = subview.frame;
CGFloat frameOrigin;
if ([sender isVertical])
{
frameOrigin = subviewFrame.origin.x;
}
else
{
frameOrigin = subviewFrame.origin.y;
}
CGFloat minimumSize =
[[lengthsByViewIndex objectForKey:[NSNumber numberWithInteger:offset]]
doubleValue];
return frameOrigin + minimumSize;
}
- (CGFloat)splitView:(NSSplitView *)sender
constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset
{
NSView *growingSubview = [[sender subviews] objectAtIndex:offset];
NSView *shrinkingSubview = [[sender subviews] objectAtIndex:offset + 1];
NSRect growingSubviewFrame = growingSubview.frame;
NSRect shrinkingSubviewFrame = shrinkingSubview.frame;
CGFloat shrinkingSize;
CGFloat currentCoordinate;
if ([sender isVertical])
{
currentCoordinate =
growingSubviewFrame.origin.x + growingSubviewFrame.size.width;
shrinkingSize = shrinkingSubviewFrame.size.width;
}
else
{
currentCoordinate =
growingSubviewFrame.origin.y + growingSubviewFrame.size.height;
shrinkingSize = shrinkingSubviewFrame.size.height;
}
CGFloat minimumSize =
[[lengthsByViewIndex objectForKey:[NSNumber numberWithInteger:offset + 1]]
doubleValue];
return currentCoordinate + (shrinkingSize - minimumSize);
}
- (void)splitView:(NSSplitView *)sender
resizeSubviewsWithOldSize:(NSSize)oldSize
{
NSArray *subviews = [sender subviews];
NSInteger subviewsCount = [subviews count];
BOOL isVertical = [sender isVertical];
CGFloat delta = [sender isVertical] ?
(sender.bounds.size.width - oldSize.width) :
(sender.bounds.size.height - oldSize.height);
NSInteger viewCountCheck = 0;
for (NSNumber *priorityIndex in
[[viewIndicesByPriority allKeys] sortedArrayUsingSelector:@selector(compare:)])
{
NSNumber *viewIndex = [viewIndicesByPriority objectForKey:priorityIndex];
NSInteger viewIndexValue = [viewIndex integerValue];
if (viewIndexValue >= subviewsCount)
{
continue;
}
NSView *view = [subviews objectAtIndex:viewIndexValue];
NSSize frameSize = [view frame].size;
NSNumber *minLength = [lengthsByViewIndex objectForKey:viewIndex];
CGFloat minLengthValue = [minLength doubleValue];
if (isVertical)
{
frameSize.height = sender.bounds.size.height;
if (delta > 0 ||
frameSize.width + delta >= minLengthValue)
{
frameSize.width += delta;
delta = 0;
}
else if (delta < 0)
{
delta += frameSize.width - minLengthValue;
frameSize.width = minLengthValue;
}
}
else
{
frameSize.width = sender.bounds.size.width;
if (delta > 0 ||
frameSize.height + delta >= minLengthValue)
{
frameSize.height += delta;
delta = 0;
}
else if (delta < 0)
{
delta += frameSize.height - minLengthValue;
frameSize.height = minLengthValue;
}
}
[view setFrameSize:frameSize];
viewCountCheck++;
}
NSAssert1(viewCountCheck == [subviews count],
@"Number of valid views in priority list is less than the subview count"
@" of split view %p.",
sender);
NSAssert3(fabs(delta) < 0.5,
@"Split view %p resized smaller than minimum %@ of %f",
sender,
isVertical ? @"width" : @"height",
sender.frame.size.width - delta);
CGFloat offset = 0;
CGFloat dividerThickness = [sender dividerThickness];
for (NSView *subview in subviews)
{
NSRect viewFrame = subview.frame;
NSPoint viewOrigin = viewFrame.origin;
viewOrigin.x = offset;
[subview setFrameOrigin:viewOrigin];
offset += viewFrame.size.width + dividerThickness;
}
}
@end

View File

@ -0,0 +1,29 @@
//
// SymbolTableController.h
// MacDependency
//
// Created by Konrad Windszus on 18.07.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class MyDocument;
@interface SymbolTableController : NSArrayController {
NSPredicate* nameFilter;
IBOutlet NSSegmentedControl* typeFilterControl;
IBOutlet NSButton* demangleNamesControl;
BOOL demangleNames;
IBOutlet MyDocument* document;
}
- (void)setNameFilter:(NSPredicate*) nameFilter;
- (NSPredicate*)nameFilter;
- (IBAction)typeFilterChanged:(id)sender;
- (BOOL)demangleNames;
- (BOOL*)demangleNamesPtr;
- (void)setDemangleNames:(BOOL)demangleNames;
@end

View File

@ -0,0 +1,120 @@
//
// SymbolTableController.m
// MacDependency
//
// Created by Konrad Windszus on 18.07.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
#import "SymbolTableController.h"
#import "MyDocument.h"
#include "MachO/symboltableentry.h"
#include "MachO/machodemangleexception.h"
@interface SymbolTableController()
-(void)setFilter;
- (NSPredicate*) typeFilter;
@end
@implementation SymbolTableController
const int TYPE[] = {SymbolTableEntry::TypeExported, SymbolTableEntry::TypeImported};
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (self) {
demangleNames = true;
}
return self;
}
// called when all connections were made
- (void)awakeFromNib {
[self setFilter];
}
- (NSPredicate*) typeFilter {
NSMutableString* typeFilter = [NSMutableString string];
for (int segment=0; segment < sizeof(TYPE)/sizeof(*TYPE); segment++) {
if ([typeFilterControl isSelectedForSegment:segment]) {
NSString* condition = [NSString stringWithFormat:@"type=%d", TYPE[segment]];
if ([typeFilter length] > 0) {
[typeFilter appendString:@" or "];
}
[typeFilter appendString:condition];
}
}
// select nothing if no filter set
NSPredicate* predicate;
if ([typeFilter length] == 0) {
predicate = [NSPredicate predicateWithValue:NO];
} else {
predicate = [NSPredicate predicateWithFormat:typeFilter];
}
return predicate;
}
- (NSPredicate*)nameFilter {
return nameFilter;
}
- (void)setNameFilter:(NSPredicate*) nameFilter {
[nameFilter retain];
[self->nameFilter release];
self->nameFilter = nameFilter;
[self setFilter];
}
- (IBAction)typeFilterChanged:(id)sender {
[self setFilter];
}
-(void)setFilter {
NSPredicate* typeFilter = [self typeFilter];
NSPredicate* predicate;
if (nameFilter) {
predicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:nameFilter, typeFilter, nil]];
} else {
predicate = typeFilter;
}
//NSLog(@"%@", predicate);
[self setFilterPredicate:predicate];
}
- (BOOL)demangleNames {
return demangleNames;
}
- (BOOL*)demangleNamesPtr {
return &demangleNames;
}
- (void)setDemangleNames:(BOOL)demangleNames {
self->demangleNames = demangleNames;
// refresh
[self rearrangeObjects];
/*// TODO: call refreshSymbols for the current architecture (over ArchitecturesController)
NSArray* objects = [dependenciesController selectedObjects];
if (objects && [objects lastObject]) {
MachOModel* model = [objects lastObject];
//[model refreshSymbols];
}*/
}
- (id)valueForKeyPath:(NSString *)keyPath {
try {
[super valueForKeyPath:keyPath];
}
catch (MachODemangleException& exc) {
NSString* error = NSLocalizedString(@"ERR_NO_DEMANGLER", nil);
[document appendLogLine:error withModel:nil state:StateError];
}
}
@end

View File

@ -0,0 +1,21 @@
//
// SymbolEntryModel.h
// MacDependency
//
// Created by Konrad Windszus on 13.07.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#include "MachO/machofile.h"
#include "MachO/symboltableentry.h"
@interface SymbolTableEntryModel : NSObject {
const SymbolTableEntry* entry;
const BOOL* demangleNames;
}
- (id) initWithEntry:(const SymbolTableEntry*) entry demangleNamesPtr:(BOOL*)demangleNames;
- (NSString*) name;
- (NSNumber*) type;
@end

View File

@ -0,0 +1,39 @@
//
// SymbolEntryModel.mm
// MacDependency
//
// Created by Konrad Windszus on 13.07.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
#import "SymbolTableEntryModel.h"
#import "MachOModel.h"
#import "ConversionStdString.h"
@implementation SymbolTableEntryModel
- (id) initWithEntry:(const SymbolTableEntry*)entry demangleNamesPtr:(BOOL*)demangleNames {
if (self = [super init]) {
self->entry = entry;
self->demangleNames = demangleNames;
}
return self;
}
- (NSString*) name {
//try {
return [NSString stringWithStdString:entry->getName(*demangleNames)];
/*} catch (MachODemangleException& e) {
// TODO: disable demangling and show error message
NSLog([NSString stringWithStdString:e.getCause()]);
return [NSString stringWithStdString:entry->getName(false)];
}*/
}
- (NSNumber*) type {
return [NSNumber numberWithUnsignedInt:entry->getType()];
}
@end

View File

@ -0,0 +1,16 @@
//
// SymbolTableEntryTypeFormatter.h
// MacDependency
//
// Created by Konrad Windszus on 18.07.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface SymbolTableEntryTypeFormatter : NSFormatter {
}
@end

View File

@ -0,0 +1,42 @@
//
// SymbolTableEntryTypeFormatter.m
// MacDependency
//
// Created by Konrad Windszus on 18.07.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
// Formatter for types of symbol table entries. We need a formatter to provide a valid sort order.
#import "SymbolTableEntryTypeFormatter.h"
#include "MachO/symboltableentry.h"
@implementation SymbolTableEntryTypeFormatter
// conversion to string
- (NSString*) stringForObjectValue:(id)obj {
// must be a NSNumber
if (![obj isKindOfClass:[NSNumber class]]) {
return nil;
}
// NSNumber contains the version as unsigned int
unsigned int typeNumber = [obj unsignedIntValue];
NSString* type;
switch(typeNumber) {
case SymbolTableEntry::TypeExported:
type = NSLocalizedString(@"SYMBOL_TYPE_EXPORT", @"Export");
break;
case SymbolTableEntry::TypeImported:
type = NSLocalizedString(@"SYMBOL_TYPE_IMPORT", @"Import");
break;
default:
type = NSLocalizedString(@"UNKNOWN", @"Unknown");
}
return type;
}
// conversion from string (not necessary)
- (BOOL) getObjectValue:(id*)obj forString:(NSString*)string errorDescription:(NSString**)errorString {
return NO;
}
@end

View File

@ -0,0 +1,22 @@
//
// TreeControllerExtension.h
// MacDependency
//
// Created by Konrad Windszus on 02.09.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
// NSTreeController-DMExtensions.h
// Library
//
// Created by William Shipley on 3/10/06.
// Copyright 2006 Delicious Monster Software, LLC. Some rights reserved,
// see Creative Commons license on wilshipley.com
// @see http://wilshipley.com/blog/2006/04/pimp-my-code-part-10-whining-about.html
#import <Cocoa/Cocoa.h>
#import "ExtTreeModel.h"
@interface NSTreeController (TreeControllerExtension)
- (BOOL)setSelectedObjects:(NSArray *)newSelectedObjects;
- (BOOL)setSelectedObject:(id <ExtTreeModel>)object;
- (NSIndexPath *)indexPathToObject:(id <ExtTreeModel>)object;
@end

View File

@ -0,0 +1,95 @@
//
// TreeControllerExtension.m
// MacDependency
//
// Created by Konrad Windszus on 02.09.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
// Extension to NSTreeController to provide methods to select an item if only its data pointer is known.
#import "TreeControllerExtension.h"
@interface NSTreeController (TreeControllerExtension_Private)
- (NSIndexPath *)_indexPathFromIndexPath:(NSIndexPath *)baseIndexPath inChildren:(NSArray **)children
toObject:(id <ExtTreeModel>)object;
@end
@implementation NSTreeController (TreeControllerExtension)
- (BOOL)setSelectedObjects:(NSArray *)newSelectedObjects
{
NSMutableArray *indexPaths = [NSMutableArray array];
unsigned int selectedObjectIndex;
for (selectedObjectIndex = 0; selectedObjectIndex < [newSelectedObjects count];
selectedObjectIndex++) {
id selectedObject = [newSelectedObjects objectAtIndex:selectedObjectIndex];
NSIndexPath *indexPath = [self indexPathToObject:selectedObject];
if (indexPath)
[indexPaths addObject:indexPath];
}
return [self setSelectionIndexPaths:indexPaths];
}
- (BOOL)setSelectedObject:(id <ExtTreeModel>)object
{
NSIndexPath *indexPath = [self indexPathToObject:object];
if (indexPath) {
return [self setSelectionIndexPath:indexPath];
}
return NO;
}
- (NSIndexPath *)indexPathToObject:(id <ExtTreeModel>)object
{
NSArray *children = [self content];
return [self _indexPathFromIndexPath:nil inChildren:&children toObject:object];
}
@end
@implementation NSTreeController (TreeControllerExtension_Private)
// inspired by: http://wilshipley.com/blog/2006/04/pimp-my-code-part-10-whining-about.html but much faster and does not rely
// on dirty and undocumented selectors of arrangedObjects.
- (NSIndexPath *)_indexPathFromIndexPath:(NSIndexPath *)baseIndexPath inChildren:(NSArray **)children
toObject:(id <ExtTreeModel>)object;
{
NSIndexPath* indexPath = nil;
// go back to root
id <ExtTreeModel> parent = [object parent];
if (parent) {
baseIndexPath = [self _indexPathFromIndexPath:baseIndexPath inChildren:children toObject:parent];
if (!baseIndexPath)
return nil;
}
// go through children (content)
unsigned int childIndex;
NSMutableArray* sortedChildren = [NSMutableArray arrayWithArray:*children];
[sortedChildren sortUsingDescriptors:[self sortDescriptors]];
for (childIndex = 0; childIndex < [sortedChildren count]; childIndex++) {
id childObject = [sortedChildren objectAtIndex:childIndex];
if ([object isEqual:childObject]) {
NSString *childrenKeyPath = [self childrenKeyPath];
NSArray* childsChildren = [childObject valueForKey:childrenKeyPath];
*children = childsChildren;
// create current NSIndex
if (!baseIndexPath) {
indexPath = [NSIndexPath indexPathWithIndex:childIndex];
} else {
indexPath = [baseIndexPath indexPathByAddingIndex:childIndex];
}
return indexPath;
}
}
return nil; // something wrong here (can't find object)
}
@end

View File

@ -0,0 +1,16 @@
//
// VersionFormatter.h
// MacDependency
//
// Created by Konrad Windszus on 17.07.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface VersionFormatter : NSFormatter {
}
@end

View File

@ -0,0 +1,40 @@
//
// VersionFormatter.m
// MacDependency
//
// Created by Konrad Windszus on 17.07.09.
// Copyright 2009 Konrad Windszus. All rights reserved.
//
// Formatter for version integers (4 byte) in the form xxxx.xx.xx
#import "VersionFormatter.h"
#define HIBYTE(x) ( (unsigned char) ((x) >> 8) )
#define LOBYTE(x) ( (unsigned char) (x) )
#define HIWORD(x) ( (unsigned short) ( (x) >> 16) )
#define LOWORD(x) ( (unsigned short) (x) )
@implementation VersionFormatter
// conversion to string
- (NSString*) stringForObjectValue:(id)obj {
// must be a NSNumber
if (![obj isKindOfClass:[NSNumber class]]) {
return nil;
}
// NSNumber contains the version as unsigned int
unsigned int version = [obj unsignedIntValue];
if (version == 0) {
return [NSString string];
}
NSString* versionString = [NSString stringWithFormat:@"%d.%d.%d", HIWORD(version), (unsigned short)HIBYTE(LOWORD(version)), (unsigned short)LOBYTE(LOWORD(version)) ];
return versionString;
}
// conversion from string (not necessary)
- (BOOL) getObjectValue:(id*)obj forString:(NSString*)string errorDescription:(NSString**)errorString {
return NO;
}
@end

14
MacDependency/main.m Normal file
View File

@ -0,0 +1,14 @@
//
// main.m
// MacDependency
//
// Created by Konrad Windszus on 19.06.09.
// Copyright Konrad Windszus 2009 . All rights reserved.
//
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
return NSApplicationMain(argc, (const char **) argv);
}

26
MachO/Info.plist Normal file
View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.googlecode.${PRODUCT_NAME:identifier}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

7
MachO/MachO_Prefix.pch Normal file
View File

@ -0,0 +1,7 @@
//
// Prefix header for all source files of the 'MachO' target in the 'MachO' project.
//
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#endif

50
MachO/demangler.cpp Normal file
View File

@ -0,0 +1,50 @@
#include "demangler.h"
#include "machodemangleexception.h"
using namespace boost::process;
/**
class for using c++flt to demangle names. Uses Boost.Process from http://www.highscore.de/cpp/process/index.html
*/
Demangler::Demangler() : child(NULL), isRunning(false)
{
init();
}
Demangler::~Demangler()
{
if (child)
child->terminate();
delete child;
}
string Demangler::demangleName(const string& name) {
if (isRunning){
(*stdin) << name << endl;
string line;
getline(*stdout, line);
return line;
} else {
throw MachODemangleException("Could not find/start process c++flt.");
}
}
void Demangler::init() {
try {
std::string exec = find_executable_in_path("c++filt3");
std::vector<std::string> args;
args.push_back("--strip-underscore");
context ctx;
ctx.environment = self::get_environment();
ctx.stdout_behavior = capture_stream();
ctx.stdin_behavior = capture_stream();
child = new boost::process::child(launch(exec, args, ctx));
stdout = &child->get_stdout();
stdin = &child->get_stdin();
isRunning = true;
// TODO: check exceptions
} catch (boost::filesystem::filesystem_error& e) {
// errors during finding executable
} catch (boost::system::system_error& e2) {
// errors during starting of process
}
}

24
MachO/demangler.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef DEMANGLER_H
#define DEMANGLER_H
#include "macho_global.h"
#include <boost/process.hpp>
class Demangler
{
public:
Demangler();
virtual ~Demangler();
string demangleName(const string& name);
private:
boost::process::child* child;
boost::process::pistream* stdout;
boost::process::postream* stdin;
bool isRunning;
void init();
private:
};
#endif // DEMANGLER_H

48
MachO/dylibcommand.cpp Normal file
View File

@ -0,0 +1,48 @@
#include "dylibcommand.h"
#include "machofile.h"
#include "machoheader.h"
#include <sstream>
#define HIBYTE(x) ( (unsigned char) ((x) >> 8) )
#define LOBYTE(x) ( (unsigned char) (x) )
#define HIWORD(x) ( (unsigned short) ( (x) >> 16) )
#define LOWORD(x) ( (unsigned short) (x) )
#define MAKEVERSION(x,y,z) 0x00000000 | (x << 16) | (y << 8) | z
DylibCommand::DylibCommand(MachOHeader* header, DependencyType type) :
LoadCommand(header), type(type)
{
file.readBytes((char*)&command, sizeof(command));
}
DylibCommand::~DylibCommand() {
}
unsigned int DylibCommand::getSize() const {
return file.getUint32(command.cmdsize);
}
string DylibCommand::getName() const {
return string(getLcDataString(command.dylib.name.offset));
}
unsigned int DylibCommand::getCurrentVersion() const {
return file.getUint32(command.dylib.current_version);
}
unsigned int DylibCommand::getCompatibleVersion() const {
return file.getUint32(command.dylib.compatibility_version);
}
time_t DylibCommand::getTimeStamp() const {
return file.getUint32(command.dylib.timestamp);
}
string DylibCommand::getVersionString(unsigned int version) {
stringstream versionString;
versionString << HIWORD(version) << "." << (unsigned short)HIBYTE(LOWORD(version)) << "." << (unsigned short)LOBYTE(LOWORD(version));
return versionString.str();
}

35
MachO/dylibcommand.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef DYLIBCOMMAND_H
#define DYLIBCOMMAND_H
#include "macho_global.h"
#include "loadcommand.h"
class MACHOSHARED_EXPORT DylibCommand : public LoadCommand
{
public:
enum DependencyType {
DependencyWeak, // dependency is allowed to be missing
DependencyDelayed, // dependency is loaded when it is needed (not at start)
DependencyNormal,
DependencyId
};
DylibCommand(MachOHeader* header, DependencyType type);
virtual ~DylibCommand();
virtual unsigned int getSize() const;
virtual unsigned int getStructureSize() const { return sizeof(command); }
bool isId() const { return type==DependencyId; }
bool isNecessary() const { return type!=DependencyWeak; }
DependencyType getType() const { return type; }
string getName() const;
unsigned int getCurrentVersion() const;
unsigned int getCompatibleVersion() const;
time_t getTimeStamp() const;
static string getVersionString(unsigned int version);
private:
dylib_command command;
DependencyType type;
};
#endif // DYLIBCOMMAND_H

314
MachO/dynamicloader.cpp Normal file
View File

@ -0,0 +1,314 @@
#include "dynamicloader.h"
#include "machofile.h"
#include "machoarchitecture.h"
#include <boost/filesystem.hpp>
/*
This class emulates the search path mechanism of dyld
http://developer.apple.com/documentation/Darwin/Reference/Manpages/man1/dyld.1.html
Unfortunately the several documents from apple contradict each other. Therefore I analyzed the source of the original dyld
http://www.opensource.apple.com/source/dyld/dyld-97.1/src/dyld.cpp
*/
const char* DynamicLoader::EnvironmentPathVariable::PATHS_SEPARATOR = ";";
const char* DynamicLoader::EnvironmentPathVariable::HOME_PATH = getenv("HOME");
DynamicLoader::EnvironmentPathVariable::EnvironmentPathVariable() {
// default constructor (should not be used explicitly)
}
DynamicLoader::EnvironmentPathVariable::EnvironmentPathVariable(const string& name, const StringList& defaultValues)
{
const char* envValue = getenv(name.c_str());
string values;
if (envValue) {
values = envValue;
}
if (!values.empty()) {
size_t start = 0;
size_t end;
while ((end = values.find(PATHS_SEPARATOR)) != string::npos) {
addPath(values.substr(start, end-start));
start = end+1;
}
} else {
setPaths(defaultValues);
}
}
void DynamicLoader::EnvironmentPathVariable::setPaths(const StringList& paths) {
this->paths = paths;
for (StringList::iterator it = this->paths.begin(); it!=this->paths.end(); ++it) {
replaceHomeDirectory(*it);
}
}
void DynamicLoader::EnvironmentPathVariable::addPath(const string& path) {
paths.push_back(path);
replaceHomeDirectory(paths.back());
}
bool DynamicLoader::EnvironmentPathVariable::replaceHomeDirectory(string& path) {
size_t homePos = path.find("~");
if (homePos != string::npos) {
path.replace(homePos, 1, HOME_PATH);
return true;
}
return false;
}
bool DynamicLoader::EnvironmentPathVariable::isEmpty() const {
if (!paths.empty()) {
return paths.front().empty();
}
return true;
}
// the order must be the order of the enum!
const char* DynamicLoader::ENVIRONMENT_VARIABLE_NAMES[DynamicLoader::NumEnvironmentVariables] = {
"LD_LIBRARY_PATH",
"DYLD_FRAMEWORK_PATH",
"DYLD_LIBRARY_PATH",
"DYLD_FALLBACK_FRAMEWORK_PATH",
"DYLD_FALLBACK_LIBRARY_PATH",
"DYLD_IMAGE_SUFFIX"
};
const char* DynamicLoader::PLACEHOLDERS[DynamicLoader::NumPlaceholders] = {
"@executable_path",
"@loader_path",
"@rpath"
};
const char* DynamicLoader::PATH_SEPARATOR = "/";
const char* DynamicLoader::DEFAULT_FRAMEWORK_PATH[] = {
"~/Library/Frameworks",
"/Library/Frameworks",
"/Network/Library/Frameworks",
"/System/Library/Frameworks"
};
const char* DynamicLoader::DEFAULT_LIBRARY_PATH[] = {
"~/lib",
"/usr/local/lib",
"/lib",
"/usr/lib"
};
// unfortunately cannot make stringList const here, but treat it as const
const StringList DynamicLoader::ENVIRONMENT_VARIABLE_DEFAULT_VALUES[DynamicLoader::NumEnvironmentVariables] = {
StringList(),
StringList(),
StringList(),
StringList(DEFAULT_FRAMEWORK_PATH, DEFAULT_FRAMEWORK_PATH + sizeof(DEFAULT_FRAMEWORK_PATH) / sizeof(*DEFAULT_FRAMEWORK_PATH)),
StringList(DEFAULT_LIBRARY_PATH, DEFAULT_LIBRARY_PATH + sizeof(DEFAULT_LIBRARY_PATH) / sizeof(*DEFAULT_LIBRARY_PATH)),
StringList()
};
DynamicLoader::DynamicLoader()
{
// init/read out some variables
for (unsigned int i=0; i < NumEnvironmentVariables; i++) {
environmentVariables[i] = EnvironmentPathVariable(ENVIRONMENT_VARIABLE_NAMES[i], ENVIRONMENT_VARIABLE_DEFAULT_VALUES[i]);
}
}
string DynamicLoader::replacePlaceholder(const string& name, const MachOArchitecture* architecture) const {
string resolvedName = name;
if (name.find(PLACEHOLDERS[ExecutablePath]) == 0) {
resolvedName.replace(0, strlen(PLACEHOLDERS[ExecutablePath]), architecture->getFile()->getExecutablePath());
} else if (name.find(PLACEHOLDERS[LoaderPath]) == 0) {
resolvedName.replace(0, strlen(PLACEHOLDERS[LoaderPath]), architecture->getFile()->getPath());
} else {
return string();
}
return resolvedName;
}
string DynamicLoader::getPathname(const string& name, const MachOArchitecture* architecture, const string& workingPath) const {
// simple name (only the last part of the name, after the last PATH_SEPARATOR)
size_t lastSlashPosition = name.rfind(PATH_SEPARATOR);
string simpleName;
if (lastSlashPosition != string::npos && lastSlashPosition < name.length() - 1) {
simpleName = name.substr(lastSlashPosition+1);
} else {
simpleName = name;
}
// try LD_LIBRARY_PATH
string pathName;
pathName = getExistingPathname(simpleName, environmentVariables[LdLibraryPath]);
if (!pathName.empty())
return pathName;
string frameworkName = getFrameworkName(name);
if (!frameworkName.empty()) {
// strip the already contained suffix
pathName = getExistingPathname(frameworkName, environmentVariables[DyldFrameworkPath]);
if (!pathName.empty())
return pathName;
}
pathName = getExistingPathname(simpleName, environmentVariables[DyldLibraryPath]);
if (!pathName.empty())
return pathName;
// resolve placeholder
string resolvedName = replacePlaceholder(name, architecture);
if (!resolvedName.empty()) {
pathName = getExistingPathname(resolvedName);
if (!pathName.empty())
return pathName;
}
if (name.find(PLACEHOLDERS[RPath]) == 0) {
// substitute @rpath with all -rpath paths up the load chain
std::vector<string*> rPaths = architecture->getRPaths();
for (std::vector<string*>::iterator it = rPaths.begin(); it != rPaths.end(); ++it) {
resolvedName = name;
resolvedName.replace(0, strlen(PLACEHOLDERS[RPath]), (**it));
pathName = getExistingPathname(resolvedName);
if (!pathName.empty())
return pathName;
}
// after checking against all stored rpaths substitute @rpath with LD_LIBRARY_PATH (if it is set)
EnvironmentPathVariable ldLibraryPaths = environmentVariables[LdLibraryPath];
if (!ldLibraryPaths.isEmpty()) {
for (StringList::const_iterator it = ldLibraryPaths.getPaths().begin(); it != ldLibraryPaths.getPaths().end(); ++it) {
resolvedName = name;
resolvedName.replace(0, strlen(PLACEHOLDERS[RPath]), (*it));
pathName = getExistingPathname(resolvedName);
if (!pathName.empty())
return pathName;
}
}
}
// check pure path (either absolute or relative to working directory)
if (name.find(PATH_SEPARATOR) == 0) {
pathName = getExistingPathname(name);
} else {
pathName = getExistingPathname(name, workingPath);
}
if (!pathName.empty())
return pathName;
// try fallbacks (or its defaults)
if (!frameworkName.empty()) {
pathName = getExistingPathname(frameworkName, environmentVariables[DyldFallbackFrameworkPath]);
if (!pathName.empty())
return pathName;
}
return getExistingPathname(name, environmentVariables[DyldFallbackLibraryPath]);
}
// returns the name is of a framework without any preceeding path information if name specifies a framework, otherwise an invalid string
string DynamicLoader::getFrameworkName(const string& name, const bool strippedSuffix) const {
// fail fast in case of dylibs
if (name.find(".framework/") == string::npos) {
return string();
}
/* first look for the form Foo.framework/Foo
next look for the form Foo.framework/Versions/A/Foo
A and Foo are arbitrary strings without a slash */
// get Foo (part after last slash)
size_t lastSlashPosition = name.rfind(PATH_SEPARATOR);
if (lastSlashPosition == string::npos || lastSlashPosition == name.length() -1) {
return false;
}
const string foo = name.substr(lastSlashPosition+1);
const string frameworkPart = foo+".framework/";
const string framework = frameworkPart + foo;
if (endsWith(name, framework)) {
// strip first part
return framework;
}
int startPosition = name.find(frameworkPart+"Versions/");
bool hasCorrectEnd = endsWith(name, foo);
// TODO: check between Versions/ and foo there must be no additional slash
if (startPosition != string::npos) {
if (hasCorrectEnd) {
return name.substr(startPosition);
} else if (strippedSuffix == false) {
// maybe we have a case, where name contains a suffix in foo (which then of course occurs only in the last foo)
// does foo already contain a suffix?
size_t suffixStart = foo.rfind("_");
if (suffixStart != string::npos) {
string newName = name;
newName.erase(lastSlashPosition+1+suffixStart);
return getFrameworkName(newName, true);
}
}
}
// if we are at this part the given name was no framework
return string();
}
string DynamicLoader::getExistingPathname(const string& name, const EnvironmentPathVariable& environmentPathVariable) const {
string result;
const StringList directories = environmentPathVariable.getPaths();
for (StringList::const_iterator it = directories.begin(); it != directories.end(); ++it) {
result = getExistingPathname(name, *it);
if (!result.empty())
return result;
}
return result;
}
string DynamicLoader::getExistingPathname(const string& file, const string& directory) const {
string name = file;
if (!directory.empty()) {
if (!endsWith(directory, "/")) {
name = directory + "/" + file;
} else {
name = directory + file;
}
}
return getExistingPathname(name);
}
string DynamicLoader::getExistingPathname(const string& name) const {
boost::filesystem::path path;
// first try with suffix
if (!environmentVariables[DyldImageSuffix].isEmpty()) {
const string suffix = environmentVariables[DyldImageSuffix].getPaths().front();
string nameWithSuffix = name;
// where should we append suffix?
if (endsWith(name, ".dylib")) {
nameWithSuffix.insert(name.rfind("."), suffix);
} else {
nameWithSuffix += suffix;
}
path = nameWithSuffix;
if (boost::filesystem::exists(path)) {
return nameWithSuffix;
}
}
// then without suffix
path = name;
if (boost::filesystem::exists(path)) {
return name;
}
return string();
}
bool DynamicLoader::endsWith(const string& str, const string& substr) {
size_t i = str.rfind(substr);
return (i != string::npos) && (i == (str.length() - substr.length()));
}

75
MachO/dynamicloader.h Normal file
View File

@ -0,0 +1,75 @@
#ifndef DYNAMICLOADER_H
#define DYNAMICLOADER_H
#include "macho_global.h"
#include <list>
typedef list<string> StringList;
class MachOArchitecture;
class DynamicLoader
{
public:
DynamicLoader();
virtual ~DynamicLoader() {}
string replacePlaceholder(const string& name, const MachOArchitecture* architecture) const;
string getPathname(const string& name, const MachOArchitecture* architecture, const string& workingDirectory) const;
private:
class EnvironmentPathVariable
{
public:
EnvironmentPathVariable();
EnvironmentPathVariable(const string& name, const StringList& defaultValues = StringList());
bool isEmpty() const;
const StringList& getPaths() const { return paths; }
private:
void setPaths(const StringList& paths);
void addPath(const string& path);
bool replaceHomeDirectory(string& path);
StringList paths;
static const char* PATHS_SEPARATOR;
static const char* HOME_PATH;
};
enum {
LdLibraryPath,
DyldFrameworkPath,
DyldLibraryPath,
DyldFallbackFrameworkPath,
DyldFallbackLibraryPath,
DyldImageSuffix,
NumEnvironmentVariables
};
enum Placeholder {
ExecutablePath,
LoaderPath,
RPath,
NumPlaceholders
};
static const char* PLACEHOLDERS[NumPlaceholders];
static const char* ENVIRONMENT_VARIABLE_NAMES[NumEnvironmentVariables];
static const char* PATH_SEPARATOR;
static const StringList ENVIRONMENT_VARIABLE_DEFAULT_VALUES[NumEnvironmentVariables];
static const char* DEFAULT_FRAMEWORK_PATH[];
static const char* DEFAULT_LIBRARY_PATH[];
EnvironmentPathVariable environmentVariables[NumEnvironmentVariables];
string getFrameworkName(const string& name, const bool strippedSuffix = false) const;
string getExistingPathname(const string& name, const EnvironmentPathVariable& environmentPathVariable) const;
string getExistingPathname(const string& name, const string& directory) const;
string getExistingPathname(const string& name) const;
static bool endsWith(const string& str, const string& substr);
};
#endif // DYNAMICLOADER_H

13
MachO/genericcommand.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "genericcommand.h"
#include "machofile.h"
#include "machoheader.h"
GenericCommand::GenericCommand(MachOHeader* header) :
LoadCommand(header)
{
file.readBytes((char*)&command, sizeof(command));
}
unsigned int GenericCommand::getSize() const {
return file.getUint32(command.cmdsize);
}

15
MachO/genericcommand.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef GENERICCOMMAND_H
#define GENERICCOMMAND_H
#include "loadcommand.h"
class GenericCommand : public LoadCommand
{
public:
GenericCommand(MachOHeader* header);
virtual unsigned int getSize() const;
private:
load_command command;
};
#endif // GENERICCOMMAND_H

97
MachO/internalfile.cpp Normal file
View File

@ -0,0 +1,97 @@
#include "internalfile.h"
#include "machoexception.h"
#include <sstream>
using namespace boost::filesystem;
// use reference counting to reuse files for all used architectures
InternalFile* InternalFile::create(InternalFile* file) {
file->counter++;
return file;
}
InternalFile* InternalFile::create(const string& filename) {
return new InternalFile(filename);
}
void InternalFile::release() {
counter--;
if (counter < 1) {
delete this;
}
}
InternalFile::InternalFile(const string& filename) :
filename(filename), counter(1)
{
// open file handle
file.open(this->filename);
if (file.fail()) {
ostringstream error;
error << "Couldn't open file '" << filename << "'.";
throw MachOException(error.str());
}
}
// destructor is private since we use reference counting mechanism
InternalFile::~InternalFile() {
file.close();
}
string InternalFile::getPath() const {
return filename.parent_path().string();
}
/* returns whole filename (including path)*/
string InternalFile::getName() const {
/* unfortunately canonized is not available in newer versions of boost filesystem.
For the reasons see the filsystem proposal at http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1889.html.
As an alternative I use realpath but I don't know if it handles unicode strings also.
*/
// try to canonicalize path
char* resolvedName = realpath(filename.file_string().c_str(), NULL);
if (!resolvedName)
return filename.file_string();
string resolvedFileName(resolvedName);
free(resolvedName);
return resolvedName;
}
/* returns filename without path */
string InternalFile::getTitle() const {
return filename.filename();
}
long long int InternalFile::getSize() const {
return file_size(filename);
}
bool InternalFile::seek(long long int position) {
file.seekg(position, ios_base::beg);
if (file.fail()) {
file.clear();
return false;
}
return true;
}
streamsize InternalFile::read(char* buffer, streamsize size) {
file.read(buffer, size);
if (file.fail()) {
file.clear();
return file.gcount();
}
// TODO: handle badbit
return size;
}
long long int InternalFile::getPosition() {
return file.tellg();
}
time_t InternalFile::getLastModificationTime() const {
return last_write_time(filename);
}

33
MachO/internalfile.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef INTERNALFILE_H
#define INTERNALFILE_H
#include "macho_global.h"
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
class InternalFile
{
public:
static InternalFile* create(InternalFile* file);
static InternalFile* create(const string& filename);
void release();
string getPath() const;
string getName() const;
string getTitle() const;
long long int getSize() const;
bool seek(long long int position);
streamsize read(char* buffer, streamsize size);
long long int getPosition();
time_t getLastModificationTime() const;
private:
unsigned int counter;
virtual ~InternalFile();
InternalFile(const string& filename);
boost::filesystem::ifstream file;
boost::filesystem::path filename;
};
#endif // INTERNALFILE_H

64
MachO/loadcommand.cpp Normal file
View File

@ -0,0 +1,64 @@
#include "loadcommand.h"
#include "dylibcommand.h"
#include "genericcommand.h"
#include "symboltablecommand.h"
#include "rpathcommand.h"
#include "machoexception.h"
#include "machoheader.h"
#include "/usr/include/mach-o/loader.h"
LoadCommand* LoadCommand::getLoadCommand(unsigned int cmd, MachOHeader* header) {
LoadCommand* loadCommand;
switch(cmd) {
case LC_LAZY_LOAD_DYLIB: // dependency is loaded when it is needed
loadCommand = new DylibCommand(header, DylibCommand::DependencyDelayed);
break;
case LC_LOAD_WEAK_DYLIB: // dependency is allowed to be missing
loadCommand = new DylibCommand(header, DylibCommand::DependencyWeak);
break;
case LC_REEXPORT_DYLIB:
case LC_LOAD_DYLIB:
loadCommand = new DylibCommand(header, DylibCommand::DependencyNormal);
break;
case LC_ID_DYLIB:
loadCommand = new DylibCommand(header, DylibCommand::DependencyId);
break;
case LC_SYMTAB:
loadCommand = new SymbolTableCommand(header);
break;
case LC_RPATH:
loadCommand = new RPathCommand(header);
break;
default:
loadCommand = new GenericCommand(header);
break;
}
return loadCommand;
}
LoadCommand::LoadCommand(MachOHeader* header) :
header(header), file(header->getFile()), offset(file.getPosition()), lcData(0)
{
}
LoadCommand::~LoadCommand() {
delete[] lcData;
}
void LoadCommand::readLcData() const {
file.seek(offset + getStructureSize());
unsigned int size = getSize() - getStructureSize();
lcData = new char[size];
file.readBytes(lcData, size);
}
// the offset is not yet in correct byte order here
const char* LoadCommand::getLcDataString(unsigned int offset) const {
if (!lcData)
readLcData();
return lcData+file.getUint32(offset)-getStructureSize();
}

32
MachO/loadcommand.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef LOADCOMMAND_H
#define LOADCOMMAND_H
#include "macho_global.h"
#include "/usr/include/mach-o/loader.h"
class MachOHeader;
class MachOFile;
class MACHOSHARED_EXPORT LoadCommand
{
protected:
LoadCommand(MachOHeader* header);
public:
static LoadCommand* getLoadCommand(unsigned int cmd, MachOHeader* header);
virtual ~LoadCommand();
virtual unsigned int getSize() const = 0;
// is smaller than getSize() because it only returns the size of the command structure
// only necessary if command uses getLcData()
virtual unsigned int getStructureSize() const { return 0; }
protected:
MachOHeader* header;
MachOFile& file;
const unsigned int offset;
const char* getLcDataString(unsigned int offset) const;
private:
mutable char* lcData;
void readLcData() const;
};
#endif // LOADCOMMAND_H

218
MachO/macho.cpp Normal file
View File

@ -0,0 +1,218 @@
#include "macho.h"
#include "machoexception.h"
#include "machofile.h"
#include "machoarchitecture.h"
#include "demangler.h"
#include "dynamicloader.h"
#include "machoheader.h"
#include <mach-o/fat.h>
// static variables
Demangler* MachO::demangler = 0;
DynamicLoader* MachO::dynamicLoader = 0;
int MachO::referenceCounter = 0;
MachO::MachO(const string& filename, const MachO* parent) : parent(parent), bundle(NULL)
{
// check if filename is bundle
string appFilename = getApplicationInBundle(filename);
init(appFilename, parent);
if (referenceCounter == 0) {
demangler = new Demangler();
dynamicLoader = new DynamicLoader();
}
referenceCounter++;
}
string MachO::getApplicationInBundle(const string& filename) {
CFURLRef bundleUrl = 0;
string appFilename = filename;
bundleUrl = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)filename.c_str() , filename.length(), true);
if (bundleUrl != NULL) {
bundle = CFBundleCreate(NULL, bundleUrl);
CFRelease(bundleUrl);
if (bundle != NULL) {
CFURLRef executableUrl = CFBundleCopyExecutableURL(bundle);
if (executableUrl != 0) {
char executableFile[FILENAME_MAX];
CFURLGetFileSystemRepresentation(executableUrl, true, (UInt8 *)executableFile, FILENAME_MAX);
appFilename = executableFile;
CFRelease(executableUrl);
//this->bundlePath = filename;
}
}
}
return appFilename;
}
void MachO::init(const string& fileName, const MachO* parent)
{
MachOFile* parentFile = 0;
if (parent) {
parentFile = parent->file;
}
file = new MachOFile(fileName, parentFile);
// read out magic number
uint32_t magic = file->readUint32();
// Universal magic is always BE
if (file->getUint32BE(magic) == FAT_MAGIC) {
// this is an universal file
// get number of architecture headers (BE)
uint32_t numberOfArchitectures = file->readUint32BE();
// read out all architecture headers
fat_arch fatArch[numberOfArchitectures];
file->readBytes((char*)fatArch, sizeof(fat_arch)*numberOfArchitectures);
// go through all architectures
for (unsigned int n=0; n < numberOfArchitectures; n++) {
unsigned int offset = file->getUint32BE(fatArch[n].offset);
file->seek(offset);
// read out magic number
uint32_t magic = file->readUint32();
file->seek(offset);
MachOArchitecture* architecture = new MachOArchitecture(*file, magic, file->getUint32BE(fatArch[n].size));
if (parent)
architecture->initParentArchitecture(parent->getCompatibleArchitecture(architecture));
architectures.push_back(architecture);
}
} else {
// seek back to beginning
file->seek(0);
MachOArchitecture* architecture = new MachOArchitecture(*file, magic, getSize());
if (parent)
architecture->initParentArchitecture(parent->getCompatibleArchitecture(architecture));
architectures.push_back(architecture);
}
}
MachO::~MachO() {
referenceCounter--;
if (referenceCounter == 0) {
delete demangler;
demangler = 0;
delete dynamicLoader;
dynamicLoader = 0;
}
for (MachOArchitecturesIterator it = architectures.begin();
it != architectures.end();
++it)
{
delete *it;
}
delete file;
if (bundle)
CFRelease(bundle);
}
// choose an architecture which is compatible to the given architecture
MachOArchitecture* MachO::getCompatibleArchitecture(MachOArchitecture* destArchitecture) const {
MachOHeader::CpuType destCpuType = destArchitecture->getHeader()->getCpuType();
// go through all architectures
for (MachOArchitecturesConstIterator it = architectures.begin();
it != architectures.end();
++it)
{
// TODO: match subtypes (only for PowerPC necessary)
if ((*it)->getHeader()->getCpuType() == destCpuType)
return *it;
}
return 0;
}
MachOArchitecture* MachO::getHostCompatibleArchitecture() const {
unsigned int destCpuType = MachOHeader::getHostCpuType();
// go through all architectures
for (MachOArchitecturesConstIterator it = architectures.begin();
it != architectures.end();
++it)
{
// TODO: match subtypes (only for PowerPC necessary)
if ((*it)->getHeader()->getCpuType() == destCpuType)
return *it;
}
return 0;
}
long long int MachO::getSize() const {
return file->getSize();
}
time_t MachO::getLastModificationTime() const {
return file->getLastModificationTime();
}
// return bundle version if available, otherwise NULL string
string MachO::getVersion() const {
string version;
if (bundle != 0) {
CFStringRef cfVersion = (CFStringRef)CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleVersionKey);
// is version available at all?
if (cfVersion) {
version = extractStringFromCFStringRef(cfVersion);
}
}
return version;
}
string MachO::getName() const {
string name;
if (bundle != 0) {
CFStringRef cfBundleName = (CFStringRef)CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleNameKey);
// is version available at all?
if (cfBundleName) {
name = extractStringFromCFStringRef(cfBundleName);
} else {
// take bundle executable name
CFURLRef bundleUrl = CFBundleCopyExecutableURL(bundle);
cfBundleName = CFURLCopyLastPathComponent(bundleUrl);
name = extractStringFromCFStringRef(cfBundleName);
CFRelease(cfBundleName);
CFRelease(bundleUrl);
}
} else {
name = file->getTitle();
}
return name;
}
string MachO::extractStringFromCFStringRef(CFStringRef cfStringRef) {
string string;
const char* szString = CFStringGetCStringPtr(cfStringRef, kCFStringEncodingASCII);
if (szString == NULL) {
CFIndex stringLength = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfStringRef), kCFStringEncodingASCII);
char szStringNew[stringLength + 1];
if (CFStringGetCString(cfStringRef,
szStringNew,
stringLength+1,
kCFStringEncodingASCII
))
string = szStringNew;
delete[] szString;
} else {
string = szString;
}
return string;
}
string MachO::getPath() const {
return file->getPath();
}
string MachO::getFileName() const { return file->getName(); }
MachO::MachOArchitecturesIterator MachO::getArchitecturesBegin() { return architectures.begin(); }
MachO::MachOArchitecturesIterator MachO::getArchitecturesEnd() { return architectures.end(); }

50
MachO/macho.h Normal file
View File

@ -0,0 +1,50 @@
#ifndef MACHO_H
#define MACHO_H
#include "macho_global.h"
#include <list>
#include <CoreFoundation/CoreFoundation.h>
class MachOFile;
class MachOArchitecture;
class Demangler;
class DynamicLoader;
class MACHOSHARED_EXPORT MachO {
private:
typedef std::list<MachOArchitecture*> MachOArchitectures;
public:
MachO(const string& fileName, const MachO* parent = 0);
~MachO();
string getFileName() const;
typedef MachOArchitectures::iterator MachOArchitecturesIterator;
typedef MachOArchitectures::const_iterator MachOArchitecturesConstIterator;
MachOArchitecturesIterator getArchitecturesBegin();
MachOArchitecturesIterator getArchitecturesEnd();
MachOArchitecture* getCompatibleArchitecture(MachOArchitecture* destArchitecture) const;
MachOArchitecture* getHostCompatibleArchitecture() const;
long long int getSize() const;
time_t getLastModificationTime() const;
string getVersion() const;
string getName() const;
//sQIcon getIcon() const;*/
const MachO* getParent() { return parent;}
string getPath() const;
static Demangler* demangler;
static DynamicLoader* dynamicLoader;
static int referenceCounter;
private:
const MachO* parent;
MachOFile* file;
MachOArchitectures architectures;
CFBundleRef bundle;
string getApplicationInBundle(const string& bundlePath);
static string extractStringFromCFStringRef(CFStringRef cfStringRef);
void init(const string& fileName, const MachO* parent);
};
#endif // MACHO_H

32
MachO/macho32header.cpp Normal file
View File

@ -0,0 +1,32 @@
#include "macho32header.h"
#include "machoexception.h"
MachO32Header::MachO32Header(const MachOFile& file, bool reversedBO) :
MachOHeader(file, reversedBO)
{
this->file.readBytes((char*)&header, sizeof(header));
}
MachOHeader::CpuType MachO32Header::getCpuType() const {
return MachOHeader::getCpuType(file.getUint32(header.cputype));
}
unsigned int MachO32Header::getInternalFileType() const {
return file.getUint32(header.filetype);
}
bool MachO32Header::is64Bit() const {
return false;
}
unsigned int MachO32Header::getNumberOfLoadCommands() const {
return file.getUint32(header.ncmds);
}
unsigned int MachO32Header::getLoadCommandSize() const {
return file.getUint32(header.sizeofcmds);;
}
unsigned int MachO32Header::getSize() const {
return sizeof(header);
}

22
MachO/macho32header.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef MACHO32HEADER_H
#define MACHO32HEADER_H
#include "machoheader.h"
class MachO32Header : public MachOHeader
{
public:
MachO32Header(const MachOFile& file, bool reversedBO);
virtual unsigned int getNumberOfLoadCommands() const;
virtual bool is64Bit() const;
virtual unsigned int getLoadCommandSize() const; // size of load command following the header
virtual unsigned int getSize() const; // size of header only
virtual MachOHeader::CpuType getCpuType() const;
protected:
virtual unsigned int getInternalFileType() const;
private:
mach_header header;
};
#endif // MACHO32HEADER_H

33
MachO/macho64header.cpp Normal file
View File

@ -0,0 +1,33 @@
#include "macho64header.h"
#include "machoexception.h"
MachO64Header::MachO64Header(const MachOFile& file, bool reversedBO) :
MachOHeader(file, reversedBO)
{
this->file.readBytes((char*)&header, sizeof(header));
}
MachOHeader::CpuType MachO64Header::getCpuType() const {
return MachOHeader::getCpuType(file.getUint32(header.cputype));
}
unsigned int MachO64Header::getInternalFileType() const {
return file.getUint32(header.filetype);
}
bool MachO64Header::is64Bit() const {
return true;
}
unsigned int MachO64Header::getNumberOfLoadCommands() const {
return file.getUint32(header.ncmds);
}
unsigned int MachO64Header::getLoadCommandSize() const {
return file.getUint32(header.sizeofcmds);
}
unsigned int MachO64Header::getSize() const {
return sizeof(header);
}

21
MachO/macho64header.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef MACHO64HEADER_H
#define MACHO64HEADER_H
#include "machoheader.h"
class MachO64Header : public MachOHeader
{
public:
MachO64Header(const MachOFile& file, bool reversedBO);
virtual unsigned int getNumberOfLoadCommands() const;
virtual bool is64Bit() const;
virtual unsigned int getLoadCommandSize() const; // size of load command following the header
virtual unsigned int getSize() const; // size of header only
virtual MachOHeader::CpuType getCpuType() const;
protected:
virtual unsigned int getInternalFileType() const;
private:
mach_header_64 header;
};
#endif // MACHO64HEADER_H

14
MachO/macho_global.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef MACHO_GLOBAL_H
#define MACHO_GLOBAL_H
#include <string>
using namespace std; // macht alles aus dem Namensraum std bekannt
#if defined(MACHO_LIBRARY)
# define MACHOSHARED_EXPORT
#else
# define MACHOSHARED_EXPORT
#endif
#endif // MACHO_GLOBAL_H

View File

@ -0,0 +1,90 @@
#include "machoarchitecture.h"
#include "machofile.h"
#include "machoheader.h"
#include "loadcommand.h"
#include "dylibcommand.h"
#include "machoexception.h"
#include "rpathcommand.h"
#include "macho.h"
#include "dynamicloader.h"
MachOArchitecture::MachOArchitecture(MachOFile& file, uint32_t magic, unsigned int size) :
header(MachOHeader::getHeader(file, magic)), file(header->getFile()), size(size), hasReadLoadCommands(false), parent(0), dynamicLibIdCommand(0)
{
}
void MachOArchitecture::initParentArchitecture(const MachOArchitecture* parent) {
this->parent = parent;
}
string MachOArchitecture::getResolvedName(const string& name, const string& workingPath) const {
string absoluteFileName = MachO::dynamicLoader->getPathname(name, this, workingPath);
if (!absoluteFileName.empty())
return absoluteFileName;
// return unresolved name if it cannot be resolved to a valid absolute name
return name;
}
std::vector<string*> MachOArchitecture::getRPaths() const {
// try to get it from the parent (recursively)
std::vector<string*> prevRPaths = parent?(std::vector<string*>(parent->getRPaths())):(std::vector<string*>());
// add own rpaths to the end
prevRPaths.insert(prevRPaths.end(), rPaths.begin(), rPaths.end());
return prevRPaths;
}
void MachOArchitecture::readLoadCommands() const {
// read out number of commands
unsigned int numberOfCommands = header->getNumberOfLoadCommands();
// read out command identifiers
for (unsigned int n=0; n<numberOfCommands; n++) {
unsigned int commandOffset = file.getPosition();
unsigned int cmd = file.readUint32();
file.seek(commandOffset);
LoadCommand* loadCommand = LoadCommand::getLoadCommand(cmd, header);
DylibCommand* dylibCommand = dynamic_cast<DylibCommand*>(loadCommand);
if (dylibCommand != 0 && dylibCommand->isId()) {
dynamicLibIdCommand = dylibCommand;
}
RPathCommand* rPathCommand = dynamic_cast<RPathCommand*>(loadCommand);
if (rPathCommand != 0) {
// try to replace placeholder
string resolvedRPath = MachO::dynamicLoader->replacePlaceholder(rPathCommand->getPath(), this);
if (resolvedRPath.empty()) {
resolvedRPath = rPathCommand->getPath();
}
rPaths.push_back(new string(resolvedRPath));
}
loadCommands.push_back(loadCommand);
file.seek(commandOffset + loadCommand->getSize());
}
hasReadLoadCommands = true;
}
MachOArchitecture::~MachOArchitecture() {
delete header;
for (LoadCommandsIterator it = loadCommands.begin();
it != loadCommands.end();
++it)
{
delete *it;
}
for (std::vector<string*>::iterator it2 = rPaths.begin();
it2 != rPaths.end();
++it2)
{
delete *it2;
}
}
unsigned int MachOArchitecture::getSize() const {
return size;
}

48
MachO/machoarchitecture.h Normal file
View File

@ -0,0 +1,48 @@
#ifndef MACHOARCHITECTURE_H
#define MACHOARCHITECTURE_H
#include "macho_global.h"
#include <list>
#include <vector>
class MachOFile;
class MachOHeader;
class LoadCommand;
class DylibCommand;
class MACHOSHARED_EXPORT MachOArchitecture
{
private:
typedef std::list<LoadCommand*> LoadCommands;
typedef LoadCommands::iterator LoadCommandsIterator;
public:
typedef LoadCommands::const_iterator LoadCommandsConstIterator;
MachOArchitecture(MachOFile& file, uint32_t magic, unsigned int size);
~MachOArchitecture();
const MachOHeader* getHeader() { return header; }
LoadCommandsConstIterator getLoadCommandsBegin() const { if(!hasReadLoadCommands) { readLoadCommands(); } return loadCommands.begin(); }
LoadCommandsConstIterator getLoadCommandsEnd() const { if(!hasReadLoadCommands) { readLoadCommands(); } return loadCommands.end(); }
DylibCommand* getDynamicLibIdCommand() const { if(!hasReadLoadCommands) { readLoadCommands(); } return dynamicLibIdCommand; }
unsigned int getSize() const;
void initParentArchitecture(const MachOArchitecture* parent);
const MachOFile* getFile() const { return &file; }
std::vector<string*> getRPaths() const;
string getResolvedName(const string& name, const string& workingPath) const;
private:
MachOHeader* header;
MachOFile& file;
const unsigned int size;
mutable bool hasReadLoadCommands;
void readLoadCommands() const;
const MachOArchitecture* parent; // architecture from which this architecture was loaded
mutable LoadCommands loadCommands;
mutable DylibCommand* dynamicLibIdCommand;
mutable std::vector<string*> rPaths;
};
#endif // MACHOARCHITECTURE_H

40
MachO/machocache.cpp Normal file
View File

@ -0,0 +1,40 @@
/*
* machocache.cpp
* MachO
*
* Created by Konrad Windszus on 13.07.09.
* Copyright 2009 __MyCompanyName__. All rights reserved.
*
*/
#include "machocache.h"
#include "macho.h"
MachOCache::MachOCache() {
}
MachOCache::~MachOCache() {
// remove all cache entries
for (CacheMapIterator it = cache.begin(); it != cache.end(); it++) {
delete it->second;
}
}
MachO* MachOCache::getFile(const string& filename, const MachO* parent) {
CacheMapIterator it = cache.find(filename);
// check if already in cache?
if (it == cache.end()) {
MachO* file = new MachO(filename, parent);
cache[filename] = file;
return file;
}
else {
return it->second;
}
}
unsigned int MachOCache::getNumEntries() {
return cache.size();
}

21
MachO/machocache.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef MACHOCACHE_H
#define MACHOCACHE_H
#include "macho_global.h"
#include <map>
class MachO;
class MachOCache
{
public:
MachOCache();
~MachOCache();
MachO* getFile(const string& filename, const MachO* parent);
unsigned int getNumEntries();
private:
typedef map<string, MachO*> CacheMap;
typedef CacheMap::iterator CacheMapIterator;
CacheMap cache;
};
#endif // MACHOCACHE_H

View File

@ -0,0 +1,6 @@
#include "machodemangleexception.h"
MachODemangleException::MachODemangleException(const string& cause) :
MachOException(cause)
{
}

View File

@ -0,0 +1,17 @@
#ifndef MACHODEMANGLEEXCEPTION_H
#define MACHODEMANGLEEXCEPTION_H
#include "macho_global.h"
#include "machoexception.h"
class MACHOSHARED_EXPORT MachODemangleException : public MachOException
{
public:
MachODemangleException(const string&);
};
#endif // MACHODEMANGLEEXCEPTION_H

6
MachO/machoexception.cpp Normal file
View File

@ -0,0 +1,6 @@
#include "machoexception.h"
MachOException::MachOException(const string& cause) :
cause(cause)
{
}

15
MachO/machoexception.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef MACHOEXCEPTION_H
#define MACHOEXCEPTION_H
#include "macho_global.h"
class MACHOSHARED_EXPORT MachOException
{
public:
MachOException(const string&);
const string& getCause() { return cause; }
private:
const string cause;
};
#endif // MACHOEXCEPTION_H

96
MachO/machofile.cpp Normal file
View File

@ -0,0 +1,96 @@
#include "machofile.h"
#include "machoexception.h"
#include "internalfile.h"
MachOFile::MachOFile(const string& filename,const MachOFile* parent, bool reversedByteOrder) :
file(InternalFile::create(filename)), position(0), reversedByteOrder(reversedByteOrder), parent(parent)
{
if (parent) {
executablePath = parent->executablePath;
} else {
executablePath = getPath();
}
}
MachOFile::MachOFile(const MachOFile& file, bool reversedByteOrder) :
file(InternalFile::create(file.file)), position(file.position), reversedByteOrder(reversedByteOrder), parent(file.parent), executablePath(file.executablePath)
{
}
MachOFile::~MachOFile() {
file->release();
}
string MachOFile::getPath() const { return file->getPath(); }
string MachOFile::getName() const { return file->getName(); }
string MachOFile::getTitle() const { return file->getTitle(); }
long long int MachOFile::getSize() const { return file->getSize(); }
time_t MachOFile::getLastModificationTime() const { return file->getLastModificationTime(); }
uint32_t MachOFile::readUint32() {
unsigned int temp;
readBytes((char*)&temp, sizeof(temp));
return getUint32(temp);
}
uint32_t MachOFile::readUint32LE() {
unsigned int temp;
readBytes((char*)&temp, sizeof(temp));
return getUint32LE(temp);
}
uint32_t MachOFile::readUint32BE() {
unsigned int temp;
readBytes((char*)&temp, sizeof(temp));
return getUint32BE(temp);
}
uint32_t MachOFile::getUint32BE(uint32_t data) {
return convertByteOrder((char*)&data, true, sizeof(data));
}
uint32_t MachOFile::getUint32LE(uint32_t data) {
return convertByteOrder((char*)&data, false, sizeof(data));
}
void MachOFile::readBytes(char* result, size_t size) {
if (file->getPosition() != position) {
file->seek(position);
}
if (file->read(result, size) != size)
throw MachOException("File '" + file->getName() + "' not big enough. Probably no valid Mach-O!");
position += size;
}
// convert from big endian or little endian to native format (Intel=little endian) and return as unsigned int (32bit)
unsigned int MachOFile::convertByteOrder(char* data, bool isBigEndian, unsigned int numberOfBytes) {
assert(numberOfBytes> 0);
assert(numberOfBytes <= 4); // max 4 byte
unsigned int result = 0;
// big endian extract (most significant byte first) (will work on little and big-endian computers)
unsigned int numberOfShifts = isBigEndian ? numberOfBytes - 1 : 0;
for (unsigned int n = 0; n < numberOfBytes; n++) {
result |= static_cast<unsigned char>(data[n]) << (8 * numberOfShifts); // the bit shift will do the correct byte order for you
numberOfShifts += isBigEndian ? -1 : +1;
}
return result;
}
uint32_t MachOFile::reverseByteOrder(uint32_t data) {
char* sourceData = (char*)&data;
uint32_t result;
char* destData = (char*)&result;
destData[3] = sourceData[0];
destData[2] = sourceData[1];
destData[1] = sourceData[2];
destData[0] = sourceData[3];
return result;
}

44
MachO/machofile.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef MACHOFILE_H
#define MACHOFILE_H
#include "macho_global.h"
class InternalFile;
class MachOFile
{
public:
MachOFile(const string& filename, const MachOFile* parent, bool reversedByteOrder = false);
MachOFile(const MachOFile& file, bool reversedByteOrder);
~MachOFile();
uint32_t readUint32();
uint32_t readUint32LE();
uint32_t readUint32BE();
void readBytes(char* result, size_t size);
uint32_t getUint32(unsigned int data) const {return (reversedByteOrder?reverseByteOrder(data):data);}
static uint32_t getUint32LE(uint32_t data);
static uint32_t getUint32BE(uint32_t data);
string getPath() const;
string getName() const;
string getTitle() const;
long long int getSize() const;
void seek(long long int offset) { position = offset; }
long long int getPosition() const { return position; }
const string& getExecutablePath() const { return executablePath; }
time_t getLastModificationTime() const;
private:
static unsigned int convertByteOrder(char* data, bool isBigEndian, unsigned int numberOfBytes);
static unsigned int reverseByteOrder(unsigned int data);
InternalFile* file;
long long int position;
const bool reversedByteOrder;
const MachOFile* parent;
string executablePath;
};
#endif // MACHOFILE_H

80
MachO/machoheader.cpp Normal file
View File

@ -0,0 +1,80 @@
#include "machoheader.h"
#include "machofile.h"
#include "macho32header.h"
#include "macho64header.h"
#include "machoexception.h"
#include <mach/mach.h>
MachOHeader* MachOHeader::getHeader(MachOFile& file, uint32_t magic) {
MachOHeader* header;
switch (magic) {
case MH_MAGIC:
header = new MachO32Header(file, false);
break;
case MH_CIGAM:
header = new MachO32Header(file, true);
break;
case MH_MAGIC_64:
header = new MachO64Header(file, false);
break;
case MH_CIGAM_64:
header = new MachO64Header(file, true);
break;
default:
throw MachOException("Invalid magic number. No Mach-O file.");
}
return header;
}
// for every header create a new filehandler with correct byte order
MachOHeader::MachOHeader(const MachOFile& file, bool reversedBO) :
file(file, reversedBO), offset(this->file.getPosition())
{
}
MachOHeader::~MachOHeader() {
}
// taken over from dyld.cpp (V 97.1)
MachOHeader::CpuType MachOHeader::getHostCpuType() {
struct host_basic_info info;
mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
mach_port_t hostPort = mach_host_self();
kern_return_t result = host_info(hostPort, HOST_BASIC_INFO, (host_info_t)&info, &count);
mach_port_deallocate(mach_task_self(), hostPort);
if ( result != KERN_SUCCESS )
throw "host_info() failed";
//sHostCPUsubtype = info.cpu_subtype;
return getCpuType(info.cpu_type);
}
MachOHeader::CpuType MachOHeader::getCpuType(unsigned int cpu) {
MachOHeader::CpuType cpuType;
switch(cpu) {
case CPU_TYPE_POWERPC:
cpuType = MachOHeader::CpuTypePowerPc;
break;
case CPU_TYPE_I386:
cpuType = MachOHeader::CpuTypeI386;
break;
case CPU_TYPE_POWERPC64:
cpuType = MachOHeader::CpuTypePowerPc64;
break;
case CPU_TYPE_X86_64:
cpuType = MachOHeader::CpuTypeX8664;
break;
default:
cpuType = MachOHeader::CpuTypeOther;
}
return cpuType;
}
MachOHeader::FileType MachOHeader::getFileType() const {
unsigned int fileType = getInternalFileType();
if (fileType > NumFileTypes || fileType < 1) {
throw MachOException("Invalid file type");
}
return static_cast<FileType>(fileType-1);
}

55
MachO/machoheader.h Normal file
View File

@ -0,0 +1,55 @@
#ifndef MACHOHEADER_H
#define MACHOHEADER_H
#include "MachO_global.h"
#include "machofile.h"
#include "/usr/include/mach-o/loader.h"
class MACHOSHARED_EXPORT MachOHeader
{
public:
static MachOHeader* getHeader(MachOFile& file, uint32_t magic);
virtual ~MachOHeader();
enum CpuType {
CpuTypePowerPc,
CpuTypeI386,
CpuTypePowerPc64,
CpuTypeX8664,
CpuTypeOther,
NumCpuTypes
};
enum FileType {
FileTypeObject, /* relocatable object file */
FileTypeExecutable, /* demand paged executable file */
FileTypeVmLib, /* fixed VM shared library file */
FileTypeCore, /* core file */
FileTypePreload, /* preloaded executable file */
FileTypeDylib, /* dynamically bound shared library */
FileTypeDylinker, /* dynamic link editor */
FileTypeBundle, /* dynamically bound bundle file */
FileTypeDylibStub, /* shared library stub for static linking only, no section contents */
FileTypeDsym, /* companion file with only debug sections */
NumFileTypes
};
FileType getFileType() const;
virtual CpuType getCpuType() const = 0;
static CpuType getHostCpuType();
static CpuType getCpuType(unsigned int internalCpuType);
virtual unsigned int getNumberOfLoadCommands() const = 0;
virtual bool is64Bit() const = 0;
MachOFile& getFile() { return file;}
unsigned int getOffset() const { return offset; }
virtual unsigned int getLoadCommandSize() const = 0; // size of load command following the header
virtual unsigned int getSize() const = 0; // size of header only
protected:
MachOHeader(const MachOFile& file, bool reversedBO);
MachOFile file;
const unsigned int offset;
virtual unsigned int getInternalFileType() const = 0;
};
#endif // MACHOHEADER_H

20
MachO/rpathcommand.cpp Normal file
View File

@ -0,0 +1,20 @@
#include "rpathcommand.h"
#include "machofile.h"
#include "machoheader.h"
RPathCommand::RPathCommand(MachOHeader* header) :
LoadCommand(header)
{
file.readBytes((char*)&command, sizeof(command));
}
RPathCommand::~RPathCommand() {
}
unsigned int RPathCommand::getSize() const {
return file.getUint32(command.cmdsize);
}
const char* RPathCommand::getPath() const {
return getLcDataString(command.path.offset);
}

20
MachO/rpathcommand.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef RPATHCOMMAND_H
#define RPATHCOMMAND_H
#include "MachO_global.h"
#include "loadcommand.h"
class MACHOSHARED_EXPORT RPathCommand : public LoadCommand
{
public:
RPathCommand(MachOHeader* header);
virtual ~RPathCommand();
virtual unsigned int getSize() const;
virtual unsigned int getStructureSize() const { return sizeof(command); }
const char* getPath() const;
private:
rpath_command command;
};
#endif // RPATHCOMMAND_H

View File

@ -0,0 +1,79 @@
#include "symboltablecommand.h"
#include "symboltableentry.h"
#include "symboltableentry32.h"
#include "symboltableentry64.h"
#include "machofile.h"
#include "machoheader.h"
SymbolTableCommand::SymbolTableCommand(MachOHeader* header) :
LoadCommand(header), symbols32(0), symbols64(0), stringTable(0)
{
file.readBytes((char*)&command, sizeof(command));
// delay loading symbol table
}
void SymbolTableCommand::readSymbolTable() const {
unsigned int stringTableLength = file.getUint32(command.strsize);
stringTable = new char[stringTableLength];
// offset relative to beginning of architecture
file.seek(header->getOffset() + file.getUint32(command.stroff));
file.readBytes(stringTable, file.getUint32(command.strsize));
unsigned int numberOfSymbols = file.getUint32(command.nsyms);
// offset relative to beginning of architecture
if (header->is64Bit()) {
symbols64 = new struct nlist_64[numberOfSymbols];
file.seek(header->getOffset() + file.getUint32(command.symoff));
file.readBytes((char*)symbols64, sizeof(*symbols64) * numberOfSymbols);
for (unsigned int n = 0; n < numberOfSymbols; n++) {
SymbolTableEntry* entry = new SymbolTableEntry64(file, &symbols64[n], stringTable);
symbolTableEntries.push_back(entry);
}
} else {
symbols32 = new struct nlist[numberOfSymbols];
file.seek(header->getOffset() + file.getUint32(command.symoff));
file.readBytes((char*)symbols32, sizeof(*symbols32) * numberOfSymbols);
for (unsigned int n = 0; n < numberOfSymbols; n++) {
SymbolTableEntry* entry = new SymbolTableEntry32(file, &symbols32[n], stringTable);
symbolTableEntries.push_back(entry);
}
}
}
SymbolTableCommand::~SymbolTableCommand()
{
for (SymbolTableEntriesIterator it = symbolTableEntries.begin();
it != symbolTableEntries.end();
++it)
{
delete *it;
}
delete[] symbols32;
delete[] symbols64;
delete[] stringTable;
}
unsigned int SymbolTableCommand::getSize() const {
return file.getUint32(command.cmdsize);
}
SymbolTableCommand::SymbolTableEntriesConstIterator SymbolTableCommand::getSymbolTableEntryBegin() const {
if (stringTable == 0)
readSymbolTable();
return symbolTableEntries.begin();
}
SymbolTableCommand::SymbolTableEntriesConstIterator SymbolTableCommand::getSymbolTableEntryEnd() const {
if (stringTable == 0)
readSymbolTable();
return symbolTableEntries.end();
}
/*
const std::vector<const SymbolTableEntry*>* SymbolTableCommand::getSymbolTableEntries() const {
if (stringTable == 0)
readSymbolTable();
return &symbolTableEntries;
}*/

View File

@ -0,0 +1,34 @@
#ifndef SYMBOLTABLECOMMAND_H
#define SYMBOLTABLECOMMAND_H
#include "loadcommand.h"
#include <mach-o/nlist.h>
#include <list>
class SymbolTableEntry;
class SymbolTableCommand : public LoadCommand
{
private:
typedef list<const SymbolTableEntry*> SymbolTableEntries;
typedef SymbolTableEntries::iterator SymbolTableEntriesIterator;
public:
typedef SymbolTableEntries::const_iterator SymbolTableEntriesConstIterator;
SymbolTableCommand(MachOHeader* header);
virtual ~SymbolTableCommand();
virtual unsigned int getSize() const;
SymbolTableEntriesConstIterator getSymbolTableEntryBegin() const;
SymbolTableEntriesConstIterator getSymbolTableEntryEnd() const;
//const std::vector<const SymbolTableEntry*>* getSymbolTableEntries() const;
private:
symtab_command command;
mutable struct nlist* symbols32;
mutable struct nlist_64* symbols64;
mutable char* stringTable;
mutable SymbolTableEntries symbolTableEntries;
void readSymbolTable() const;
};
#endif // SYMBOLTABLECOMMAND_H

View File

@ -0,0 +1,48 @@
#include "symboltableentry.h"
#include "macho.h"
#include "machofile.h"
#include "demangler.h"
SymbolTableEntry::SymbolTableEntry(MachOFile& file, char* stringTable) :
file(file), stringTable(stringTable)
{
}
SymbolTableEntry::~SymbolTableEntry() {
}
string SymbolTableEntry::getName(bool shouldDemangle) const {
const char* internalName = getInternalName();
if (shouldDemangle) {
return MachO::demangler->demangleName(internalName);
}
return internalName;
}
SymbolTableEntry::Type SymbolTableEntry::getType() const {
unsigned int type = getInternalType();
if (type & N_STAB) {
return TypeDebug;
}
if (type & N_PEXT) {
return TypePrivateExtern;
}
if (type & N_EXT) {
if ((type & N_TYPE) == N_UNDF)
return TypeImported;
else
return TypeExported;
}
else
return TypeLocal;
}
/*
QDebug& operator<<(QDebug& dbg, const SymbolTableEntry& symbolTable) {
unsigned int type = symbolTable.getInternalType();
dbg.nospace() << "Name:" << symbolTable.getInternalName() << "Type" << (type & N_TYPE) << "STAB" << (type & N_STAB) << "PEXT" << (type & N_PEXT) << "NTYPE" << (type & N_TYPE) << "NEXT" << (type & N_EXT);
return dbg.space();
}
*/

36
MachO/symboltableentry.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef SYMBOLTABLEENTRY_H
#define SYMBOLTABLEENTRY_H
#include "macho_global.h"
#include <mach-o/nlist.h>
class MachOFile;
class SymbolTableEntry
{
public:
SymbolTableEntry(MachOFile& file, char* stringTable);
virtual ~SymbolTableEntry();
string getName(bool shouldDemangle) const;
enum Type {
TypeExported = 0,
TypeImported,
TypeLocal,
TypeDebug,
TypePrivateExtern,
NumTypes
};
Type getType() const;
virtual unsigned int getInternalType() const = 0;
virtual const char* getInternalName() const = 0;
protected:
MachOFile& file;
char* stringTable;
};
//QDebug &operator<<(QDebug &dbg, const SymbolTableEntry &symbolTable);
#endif // SYMBOLTABLEENTRY_H

View File

@ -0,0 +1,17 @@
#include "symboltableentry32.h"
#include "machofile.h"
SymbolTableEntry32::SymbolTableEntry32(MachOFile& file, struct nlist* entry, char* stringTable) :
SymbolTableEntry(file, stringTable), entry(entry)
{
}
const char* SymbolTableEntry32::getInternalName() const {
return &stringTable[file.getUint32(entry->n_un.n_strx)];
}
unsigned int SymbolTableEntry32::getInternalType() const {
return entry->n_type;
}

View File

@ -0,0 +1,17 @@
#ifndef SYMBOLTABLEENTRY32_H
#define SYMBOLTABLEENTRY32_H
#include "symboltableentry.h"
class SymbolTableEntry32 : public SymbolTableEntry
{
public:
SymbolTableEntry32(MachOFile& file, struct nlist* entry, char* stringTable);
virtual const char* getInternalName() const;
virtual unsigned int getInternalType() const;
private:
struct nlist* entry;
};
#endif // SYMBOLTABLEENTRY32_H

View File

@ -0,0 +1,16 @@
#include "symboltableentry64.h"
#include "machofile.h"
SymbolTableEntry64::SymbolTableEntry64(MachOFile& file, struct nlist_64* entry, char* stringTable) :
SymbolTableEntry(file, stringTable), entry(entry)
{
}
const char* SymbolTableEntry64::getInternalName() const {
return &stringTable[file.getUint32(entry->n_un.n_strx)];
}
unsigned int SymbolTableEntry64::getInternalType() const {
return entry->n_type;
}

View File

@ -0,0 +1,16 @@
#ifndef SYMBOLTABLEENTRY64_H
#define SYMBOLTABLEENTRY64_H
#include "symboltableentry.h"
class SymbolTableEntry64 : public SymbolTableEntry
{
public:
SymbolTableEntry64(MachOFile& file, struct nlist_64* entry, char* stringTable);
virtual const char* getInternalName() const;
virtual unsigned int getInternalType() const;
private:
struct nlist_64* entry;
};
#endif // SYMBOLTABLEENTRY64_H