//AppController.h
/*
* AppController.h
* MachOView
*
* Created by psaghelyi on 15/06/2010.
*
*/
#import <Cocoa/Cocoa.h>
@class MVPreferenceController;
@interface MVAppController : NSObject <NSApplicationDelegate,NSOpenSavePanelDelegate>
{
MVPreferenceController * preferenceController;
}
//偏好设置
- (IBAction)showPreferencePanel:(id)sender;
//附加
- (IBAction)attach:(id)sender;
@end
//AppController.mm
/*
* AppController.mm
* MachOView
*
* Created by psaghelyi on 15/06/2010.
*
*/
#import "Common.h"
#import "AppController.h"
#import "DataController.h"
#import "Document.h"
#import "PreferenceController.h"
#import "Attach.h"
// counters for statistics
int64_t nrow_total; // number of rows (loaded and empty)
int64_t nrow_loaded; // number of loaded rows
//============================================================================
@implementation MVAppController
//----------------------------------------------------------------------------
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
{
return NO;
}
//----------------------------------------------------------------------------
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
return NO;
}
//----------------------------------------------------------------------------
//new 方式 没写
- (IBAction)newDocument:(id)sender
{
NSLog(@"Not yet possible");
}
//----------------------------------------------------------------------------
//是否在运行
- (BOOL)isOnlyRunningMachOView
{
//获取进程的相关信息
NSProcessInfo * procInfo = [NSProcessInfo processInfo];
//获取程序程序包(app包就是一个mainBundle)
NSBundle * mainBundle = [NSBundle mainBundle];
//获取当前进程的CFBundleShortVersionString
//返回指定键关联的接收器中的属性信息列表
NSString * versionString = [mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
NSUInteger numberOfInstance = 0;
//返回一个工作空间
//提供服务
//1)打开,操作文件/设备,获取文件/设备信息
//2)跟踪文件,设备以及数据库的变动
//3)设置或获取文件的 Finder 信息
//4)操作应用程序
NSWorkspace * workspace = [NSWorkspace sharedWorkspace];
//获取当前运行的 apps
for (NSRunningApplication * runningApplication in [workspace runningApplications])
{
//检查进程名称是否匹配
//executableURL获取bundle 类实例的 可执行的URL
//lastPathComponent获取路径中的最后一个文件名
NSString * fileName = [[runningApplication executableURL] lastPathComponent];
//判断名称是否相等
if ([fileName isEqualToString: [procInfo processName]] == NO)
{
continue;
}
//检查版本字符串是否匹配
//通过 url 获取bundle
NSBundle * bundle = [NSBundle bundleWithURL:[runningApplication bundleURL]];
//判断是否与当前进程的相等
if ([versionString isEqualToString:[bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]] == YES && ++numberOfInstance > 1)
{
return NO;
}
}
return YES;
}
//----------------------------------------------------------------------------
//附加方式打开同时解析 mach-o header
- (IBAction)attach:(id)sender
{
//alert
NSAlert *alert = [NSAlert alertWithMessageText:@"Insert PID to attach to:"
defaultButton:@"Attach"
alternateButton:@"Cancel"
otherButton:nil
informativeTextWithFormat:@""];
//初始化一个文本框
NSTextField *input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)];
//设置默认字符串
[input setStringValue:@""];
//设置弹框的 view为input
[alert setAccessoryView:input];
//返回按钮值
NSInteger button = [alert runModal];
if (button == NSAlertDefaultReturn)
{
//设置为不可编辑
[input validateEditing];
//获取 pid
pid_t targetPid = [input intValue];
NSLog(@"Attach to process %d", targetPid);
//入口地址
mach_vm_address_t mainAddress = 0;
//找到入口地址
if (find_main_binary(targetPid, &mainAddress))
{
NSLog(@"Failed to find main binary address!");
return;
}
uint64_t aslr_slide = 0;
uint64_t imagesize = 0;
//获取镜像大小
if ( (imagesize = get_image_size(mainAddress, targetPid, &aslr_slide)) == 0 )
{
NSLog(@"[ERROR] Got image file size equal to 0!");
return;
}
//分配macho所需要的缓冲区大小(文件)
uint8_t *readbuffer = (uint8_t*)malloc(imagesize);
if (readbuffer == NULL)
{
NSLog(@"Can't allocate mem for dumping target!");
return;
}
//最后读取这些部分并将它们的内容转储到缓冲区中
if (dump_binary(mainAddress, targetPid, readbuffer, aslr_slide))
{
NSLog(@"Main binary memory dump failed!");
free(readbuffer);
return;
}
//将缓冲区内容转储到临时文件以使用NSDocument model
//拼接临时文件名+进程名 如:"/var/folders/l8/3kn0r51s3qdbbnxb9ng2gvb40000gn/T/MachOView_2.4.XXXXXXXXXXX"
const char *tmp = [[MVDocument temporaryDirectory] UTF8String];
//用于保存数数据的临时文件绝对路径名的缓冲区
char *dumpFilePath = (char*)malloc(strlen(tmp)+1);
if (dumpFilePath == NULL)
{
NSLog(@"Can't allocate mem for temp filename path!");
free(readbuffer);
return;
}
//拷贝
strcpy(dumpFilePath, tmp);
int outputFile = 0;
//在系统中以唯一的文件名创建一个文件并打开
if ( (outputFile = mkstemp(dumpFilePath)) == -1 )
{
NSLog(@"mkstemp failed!");
free(dumpFilePath);
free(readbuffer);
return;
}
//写入
if (write(outputFile, readbuffer, imagesize) == -1)
{
NSLog(@"[ERROR] Write error at %s occurred!\n", dumpFilePath);
free(dumpFilePath);
free(readbuffer);
return;
}
NSLog(@"\n[OK] Full binary dumped to %s!\n\n", dumpFilePath);
close(outputFile);
//打开文件
[self application:NSApp openFile:[NSString stringWithCString:dumpFilePath encoding:NSUTF8StringEncoding]];
//删除临时转储文件
NSFileManager * fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtPath:[NSString stringWithCString:dumpFilePath encoding:NSUTF8StringEncoding] error:NULL];
free(dumpFilePath);
free(readbuffer);
}
else if (button == NSAlertAlternateReturn)
{
//
}
else
{
NSAssert1(NO, @"Invalid input dialog button %ld", button);
}
}
//----------------------------------------------------------------------------
//open方式打开
- (IBAction)openDocument:(id)sender
{
//打开文件查看
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
//是否显示面板封装的文件的目录
[openPanel setTreatsFilePackagesAsDirectories:YES];
//设置为多选模式
[openPanel setAllowsMultipleSelection:YES];
//不可选择文件夹
[openPanel setCanChooseDirectories:NO];
//设置可以选择文件
[openPanel setCanChooseFiles:YES];
//设置代理为自身 调用 shouldShowFilename过滤打开面板中的文件
[openPanel setDelegate:self];
//告警风格窗口
//completionHandler参数为事件响应
[openPanel beginSheetModalForWindow:nil
completionHandler:^(NSInteger result)
{
//判断点击 ok
if (result != NSOKButton)
{
return;
}
//关闭面板之前,可能会出现错误
//移除窗口
[openPanel orderOut:self];
//遍历所选的文件路径
for (NSURL * url in [openPanel URLs])
{
//全部打开(通过多文档模式)
[self application:NSApp openFile:[url path]];
}
}];
}
//----------------------------------------------------------------------------
//协议-----过滤文件
- (BOOL)panel:(id)sender shouldShowFilename:(NSString*)filename
{
//文件 url
NSURL * url = [NSURL fileURLWithPath:filename];
// 判断是否为文件夹
NSNumber * isDirectory = nil;
[url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL];
if ([isDirectory boolValue] == YES)
{
return YES;
}
// 跳过符号链接等
//判断是否为一些符号链接
NSNumber * isRegularFile = nil;
[url getResourceValue:&isRegularFile forKey:NSURLIsRegularFileKey error:NULL];
if ([isRegularFile boolValue] == NO)
{
return NO;
}
// 检查文件的magic
//文件句柄
NSFileHandle * fileHandle = [NSFileHandle fileHandleForReadingAtPath:filename];
// NSLog(@"fileHandleForReadingAtPath----- %@!\n\n", filename);
//读取指定的字符数
NSData * magicData = [fileHandle readDataOfLength:8];
// NSLog(@"magicData----- %@!\n\n", magicData);
//关闭句柄
[fileHandle closeFile];
//判断长度
if ([magicData length] < sizeof(uint32_t))
{
return NO;
}
//比较
uint32_t magic = *(uint32_t*)[magicData bytes];
//NSLog(@"magic----- %8x!\n\n", magic);
if (magic == MH_MAGIC || magic == MH_MAGIC_64 ||
magic == FAT_CIGAM || magic == FAT_MAGIC)
{
return YES;
}
if ([magicData length] < sizeof(uint64_t))
{
return NO;
}
if (*(uint64_t*)[magicData bytes] == *(uint64_t*)"!<arch>\n")
{
return YES;
}
return NO;
}
//----------------------------------------------------------------------------
//程序将启动
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
BOOL isFirstMachOView = [self isOnlyRunningMachOView];
//禁用状态恢复功能,这对MachOView来说不是很有用
if([[NSUserDefaults standardUserDefaults] objectForKey: @"ApplePersistenceIgnoreState"] == nil)
[[NSUserDefaults standardUserDefaults] setBool: YES forKey:@"ApplePersistenceIgnoreState"];
// load user's defaults for preferences
// if([[NSUserDefaults standardUserDefaults] objectForKey: @"UseLLVMDisassembler"] != nil)
// qflag = [[NSUserDefaults standardUserDefaults] boolForKey:@"UseLLVMDisassembler"];
//获取一个文件管理者
NSFileManager * fileManager = [NSFileManager defaultManager];
NSString * tempDir = [MVDocument temporaryDirectory];
__autoreleasing NSError * error;
//删除以前被遗忘的临时文件
if (isFirstMachOView && [fileManager fileExistsAtPath:tempDir isDirectory:NULL] == YES)
{
//移除
if ([fileManager removeItemAtPath:tempDir error:&error] == NO)
{
[NSApp presentError:error];
}
}
//为临时文件创建占位符
//返回一个布尔值,该值指示一个文件或目录是否存在于指定路径
if ([fileManager fileExistsAtPath:tempDir isDirectory:NULL] == NO)
{
//创建一个带有给定属性的指定目录路径
if ([fileManager createDirectoryAtPath:tempDir
withIntermediateDirectories:NO
attributes:nil
error:&error] == NO)
{
//调用错误对话框
[NSApp presentError:error];
}
}
}
//----------------------------------------------------------------------------
//程序已启动
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
#ifdef MV_STATISTICS
nrow_total = nrow_loaded = 0;
//开线程开始打印(用于调试忽视)
[NSThread detachNewThreadSelector:@selector(printStat) toTarget:self withObject:nil];
#endif
//默认一开始就打开文件对话框
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"OpenAtLaunch"] != nil)
{
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"OpenAtLaunch"] == YES)
{
if ([[[NSDocumentController sharedDocumentController] documents] count] == 0)
{
[self openDocument:nil];
}
}
}
}
//----------------------------------------------------------------------------
//程序将结束
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
BOOL isLastMachOView = [self isOnlyRunningMachOView];
if (isLastMachOView == YES)
{
//删除临时文件
NSFileManager * fileManager = [NSFileManager defaultManager];
//返回当前用户的临时目录
NSString * tempDir = [MVDocument temporaryDirectory];
//NSLog(@"Can't allocate mem for temp filename path! %@",tempDir);
//删除
[fileManager removeItemAtPath:tempDir error:NULL];
}
}
//----------------------------------------------------------------------------
//回调函数 打开文件
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
{
NSLog (@"open file: %@", filename);
__autoreleasing NSError *error;
//获取一个多文档控制器
NSDocumentController * documentController = [NSDocumentController sharedDocumentController];
//NSDocumentController 创建管理多个MVDocument
//MVDocument 创建管理多个NSWindowController
//NSWindowController
//通过文件路径创建一个文档 同时显示(通过代理实现细节)
//MVDocument必须实现的函数有:
//窗口名称
//- (NSString *)windowNibName;
//指定窗口控制器
//- (void)windowControllerDidLoadNib:(NSWindowController *)aController;
//必须实现创建和返回受支持类型的文档数据,通常是将数据写入NSData对象的文件。
//- (NSData *)dataRepresentationOfType:(NSString *)aType;
//必须实现将NSData对象(包含特定类型的文档数据)转换为文档的内部数据结构,以便文档准备显示其内容。NSData对象通常是从读取文档文件的文档中产生的。
//- (BOOL)loadDataRepresentation:(NSData *)data:(NSString *)aType;
MVDocument * document = [documentController openDocumentWithContentsOfURL:[NSURL fileURLWithPath:filename]
display:YES
error:&error];
//判断是否能打开文档
if (!document)
{
[NSApp presentError:error];
return NO;
}
return YES;
}
//----------------------------------------------------------------------------
//线程回调
-(void) printStat
{
for (;;)
{
NSLog(@"stat: %lld/%lld rows in memory\n",nrow_loaded,nrow_total);
[NSThread sleepForTimeInterval:1];
}
}
//----------------------------------------------------------------------------
//系统偏好设置 (显示一个nib)
- (IBAction)showPreferencePanel:(id)sender
{
if (!preferenceController)
{
preferenceController = [[MVPreferenceController alloc] init];
}
[preferenceController showWindow:self];
}
@end
转载于:https://blog.51cto.com/haidragon/2142007