2011年7月13日 星期三

iOS 開發教學 - 關於 Crash Report 和 dSYM 的使用

前幾天玩遊戲時,看到開發者請大家把 Crash Log / Crash Report / Bug Report 寄回給他,如此一來就可以偵錯,滿好奇的便也隨意在網路上找找該怎樣使用,在此便稍作筆記,並且寫一個簡單會 crash 的程式(很少人會這樣做吧 XD)。

所謂的 Crash Report 是在實體機器上,執行程式時發生 crash 時,此時 iOS 會幫忙將程式出錯的相關資訊存起來,這時候用 Xcode 的 Organizer - Devices 時,可以去點選設備,並且可以查看有哪些 report 在上頭,這時候就可以稍作觀看,但是上頭記錄的資訊是有稍微類似做過保護的,只會看到類似(這是網路上收集的資訊,但我嘗試做出的 log 好像已經轉好了?):

Thread 0 Crashed:
0   libSystem.B.dylib              0x35de3ad0 0x######## + ##
1   libSystem.B.dylib              0x35de3abe 0x######## + ##
2   libSystem.B.dylib              0x35de3ab2 0x######## + ##
3   libSystem.B.dylib              0x35dfad5e 0x######## + ##
4   libSystem.B.dylib              0x35de967e 0x######## + ##
5   libSystem.B.dylib              0x35de974c 0x######## + ##
6   libSystem.B.dylib              0x35d5c8e0 0x######## + ##
7   libSystem.B.dylib              0x35d5c798 0x######## + ##
8   CoreFoundation                 0x3750022c 0x######## + ##
9   CoreFoundation                 0x374fc206 0x######## + ##
10  CoreFoundation                 0x37504564 0x######## + ##
11  CoreFoundation                 0x37504406 0x######## + ##
12  Foundation                     0x351530f0 0x######## + ##
13  CrashTesting                   0x00002712 0x######## + ##
14  UIKit                          0x35924fd0 0x######## + ##
15  UIKit                          0x3591ea70 0x######## + ##
16  UIKit                          0x358d82dc 0x######## + ##
17  UIKit                          0x358d7b14 0x######## + ##
18  UIKit                          0x358d73ac 0x######## + ##
19  GraphicsServices               0x33e77c80 0x######## + ##
20  CoreFoundation                 0x3752f5c4 0x######## + ##
21  CoreFoundation                 0x3752f582 0x######## + ##
22  CoreFoundation                 0x3752182e 0x######## + ##
23  CoreFoundation                 0x37521504 0x######## + ##
24  CoreFoundation                 0x37521412 0x######## + ##
25  UIKit                          0x3591d54c 0x######## + ##
26  UIKit                          0x3591a550 0x######## + ##
27  CrashTesting                   0x0000266e 0x######## + ##
28  CrashTesting                   0x00002620 0x######## + ##

這時候需要使用原先編譯 app 時,在同一目錄中會產生對應的 dSYM 檔案,搭配 dwarfdump 使用才可以查詢到真正的片段程式(例如想看 line 13 的意思):

$ dwarfdump --lookup 0x00002712 -arch armv6 CrashTesting.app.dSYM/
----------------------------------------------------------------------
 File: CrashTesting.app.dSYM/Contents/Resources/DWARF/CrashTesting (armv6)
----------------------------------------------------------------------
Looking up address: 0x0000000000002712 in .debug_info... found!

0x00000132: Compile Unit: length = 0x000049b0  version = 0x0002  abbr_offset = 0x00000000  addr_size = 0x04  (next CU at 0x00004ae6)

0x0000013d: TAG_compile_unit [1] *
             AT_producer( "4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2373.6)" )
             AT_language( DW_LANG_ObjC )
             AT_name( "CrashTestingAppDelegate.m" )
             AT_entry_pc( 0x000026b4 )
             AT_stmt_list( 0x0000006b )
             AT_comp_dir( "/Volumes/Data/iPhoneDev/trunk/CrashTesting/CrashTesting" )
             AT_APPLE_major_runtime_vers( 0x02 )

0x00000166:     TAG_subprogram [16] *
                 AT_sibling( {0x000001d2} )
                 AT_name( "-[CrashTestingAppDelegate application:didFinishLaunchingWithOptions:]" )
                 AT_decl_file( "/Volumes/Data/iPhoneDev/trunk/CrashTesting/CrashTesting/CrashTestingAppDelegate.m" )
                 AT_decl_line( 17 )
                 AT_prototyped( 0x01 )
                 AT_type( {0x0000015b} ( BOOL ) )
                 AT_APPLE_isa( 0x01 )
                 AT_low_pc( 0x000026b4 )
                 AT_high_pc( 0x00002780 )
                 AT_frame_base( r7 )

0x000001b9:         TAG_lexical_block [5] *
                     AT_low_pc( 0x000026c4 )
                     AT_high_pc( 0x00002780 )
Line table file: 'CrashTestingAppDelegate.m' line 20, column 0 with start address 0x000000000000270e

Looking up address: 0x0000000000002712 in .debug_frame... found!

0x00000020: FDE
        length: 0x0000000c
   CIE_pointer: 0x00000000
    start_addr: 0x000026b4 -[CrashTestingAppDelegate application:didFinishLaunchingWithOptions:]
    range_size: 0x000000cc (end_addr = 0x00002780)
  Instructions: 0x000026b4: CFA=4294967295+4294967295

因此就可以查看到 line 13 是指 CrashTestingAppDelegate.m' line 20,所以就可以去那邊追查看看。

以上的例子僅需建立一個 Project (此例名為 CrashTesting),然後在 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 裡頭寫一段會 crash 的程式碼(此例為 double free):


    NSString *justCrash = [[NSString alloc] initWithFormat:@"CrashTesting"];
    [justCrash release];
    [justCrash release];

如此一來,在實機執行完並當機後,可以接上 Xcode 去 Organizer - Devices 查看:

xcode-crash-log

而 dSYM 檔案,則是在 Xcode 編譯的地方:

xcode-dSYM

點選後,就可以看到並且可以複製到習慣的工作目錄囉

xcode-app-dSYM

參考資料:


iPhone/iPad 遊戲 - 大家來搶錢 HD

大家來搶錢 HD-0 大家來搶錢 HD-1


前陣子趁著限時免費時,下載了這款遊戲(現在要價 1.99 美金)。後來過了一陣子後,同事一直在玩也推薦我玩,就不小心也陷入想要把地圖全破的慾念中。這款就像是兒時 PC 上的大富翁遊戲,可以選地圖、餐與人物,遊戲過程中藉由買土地、建房子、收過路費等模式進行,除此之外,有簡單的卡片系統,可以買特殊的卡片,如停留卡、購地卡、請神卡、均富卡等等,當然還有四個神仙供你參拜以及錢滾錢的股票系統。


大家來搶錢 HD-2


目前共提供 10 個地圖,玩起來還不錯,就跟 PC 上的大富翁很像,只是沒那麼複雜。這幾天開始試試股票系統,今天不小心就從 4 塊錢買進,遊戲進行中又使用了幾張紅卡後,目前市值已經有 99 元,使得 UI 顯示已經爆表看不到股票現值了 XD 然後定期會有股票現金股利吧?不知不覺手頭上的現金也蹦出一堆。我想,我應該可以正式從這款遊戲畢業了 XDD


大家來搶錢 HD-3


這款應該是香港或大陸人開發的遊戲吧?我覺得品質還不錯,隨著地圖有對應的風格背景、遊戲聲效等,最近好像有更新軟體?或許修正了一些問題或增加難度了吧。雖然我對遊戲開發還沒有熱情,但透過這款遊戲的刺激,哪天也來研究一下遊戲吧!總覺得這款是透過某種機制 porting 到 iPhone 上頭,如果也是走 OpenGL ES 的話,或許 Android 也能無痛 porting 呢。


最後一提,這款有支援 iPad 喔,算挺不賴的!


2011年7月11日 星期一

iPhone 開發教學 - A Simple Socket Server Example


來源:Introduction to CFNetwork Programming Guide


前陣子用了一些 iPhone app,其中有些 app 有提供小型的 web service,因此讓我想練習 iOS socket programming,反正暑假也到了,我就抽點時間東看西看。


花了不少時間,才擠出一點小範例。在 iOS 上寫 socket server 的方式,跟一般工作站寫的不一樣,以前在工作站寫 server 時,因為一開始執行並不會有 client 馬上連線,所以要將 server 程式用 loop 等著,以免程式終止。在 iOS 上寫 socket server 則有點像 Web 上寫 AJAX 的模式,改成 EVENT-DRIVEN 架構,變成要註冊 client 連線事件、client 傳輸資料、client 終止連線等相關事件。這些大概是跟 UI 操作架構有關,因此在 iOS 上寫 socket server 時,不需再用 loop 架構等在那邊(若用 loop 等在那邊也容易出錯)。


建立 iOS socket server 流程如下:



  1. 初始化 socket server 資訊,如使用的 IP 和 Port number

  2. 設定 socket accept 事件處理

  3. 設定 socket connect 事件處理

  4. 設定 socket close 事件處理


雖然 iOS 支援 BSD Socket,只是還是用 BSD Socket 練習,那就會沒學到新東西,所以此處採用稍微上層的 Core Foundation/CFNetwork 架構,物件上採用 CFSocket,此例是在 iPhone Simulator 上運行一支 socket server ,然後透過 cmd line 的 telnet 進行連線測試。僅須把程式碼都寫在 YourProjectAppDelegate.m 之裡頭即可(如:SocketServer)。


AddCFNetworkLib
記得要新增 CFNetwork framework


程式碼:


#import <CFNetwork/CFNetwork.h>
#import <arpa/inet.h>
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <netdb.h>

// Global variables 
CFSocketRef server;
int serverPort = 5566;
CFMutableDictionaryRef incomingRequests;
NSFileHandle *listeningHandle;


- (void)clientHandleClose:(NSFileHandle *)incomingFileHandle close:(BOOL)closeFileHandle
{
NSLog(@"socket close");
if (closeFileHandle)
{
[incomingFileHandle closeFile];
}

[[NSNotificationCenter defaultCenter] removeObserver:self name:NSFileHandleDataAvailableNotification object:incomingFileHandle];
CFDictionaryRemoveValue(incomingRequests, incomingFileHandle);

}

- (void)clientDataReceiveNotification:(NSNotification *)notification
{
NSLog(@"receive data");
NSFileHandle *incomingFileHandle = [notification object];
NSData *data = [incomingFileHandle availableData];

if ([data length] == 0)
{
[self clientHandleClose:incomingFileHandle close:NO];
return;
}
NSString *formatedData = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(@": %@", formatedData);
[formatedData release];
    
[self clientHandleClose:incomingFileHandle close:YES];
}

- (void)clientAcceptNotification:(NSNotification *)notification
{
NSLog(@"server accept");
    
NSDictionary *userInfo = [notification userInfo];
NSFileHandle *incomingFileHandle = [userInfo objectForKey:NSFileHandleNotificationFileHandleItem];
    
if(incomingFileHandle)
{
CFDictionaryAddValue( incomingRequests, incomingFileHandle, [(id)CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE) autorelease]);

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(clientDataReceiveNotification:) name:NSFileHandleDataAvailableNotification object:incomingFileHandle];
[incomingFileHandle waitForDataInBackgroundAndNotify];
}

    
[listeningHandle acceptConnectionInBackgroundAndNotify];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
int socketSetupContinue = 1;
struct sockaddr_in addr;
    
if( !(server = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM,IPPROTO_TCP, 0, NULL, NULL) ) )
{
NSLog(@"CFSocketCreate failed");
socketSetupContinue = 0;
}
    
if( socketSetupContinue )
{
int yes = 1;
if( setsockopt(CFSocketGetNative(server), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(int)) )
{
NSLog(@"setsockopt failed");
CFRelease(server);
socketSetupContinue = 0;
}
}
    
if( socketSetupContinue )
{
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(struct sockaddr_in);
addr.sin_family = AF_INET;
addr.sin_port = htons(serverPort);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
        
//CFDataRef *address = CFDataCreate(NULL, (const UInt8 *)&addr, sizeof(addr));
//[(id)address autorelease];
//if (CFSocketSetAddress(server, address) != kCFSocketSuccess) 
NSData *address = [NSData dataWithBytes:&addr length:sizeof(addr)];
if (CFSocketSetAddress(server, (CFDataRef)address) != kCFSocketSuccess) 
{
NSLog(@"CFSocketSetAddress failed");
CFRelease(server);
socketSetupContinue =0;
}
}
    
if( socketSetupContinue )
{
incomingRequests = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
listeningHandle = [[NSFileHandle alloc] initWithFileDescriptor:CFSocketGetNative(server) closeOnDealloc:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(clientAcceptNotification:) name:NSFileHandleConnectionAcceptedNotification object:nil];
[listeningHandle acceptConnectionInBackgroundAndNotify];

        
NSLog(@"Socket listening on %s:%d", addr2ascii(AF_INET, &(addr.sin_addr.s_addr),sizeof(addr.sin_addr.s_addr),NULL), serverPort);
}
    
// Override point for customization after application launch.
// Add the navigation controller's view to the window and display.
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
return YES;
}


模擬器一開啟後,就會聽在 5566 port 上頭,接著可以用 telnet localhost 進行測試:


$ telnet localhost 5566
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.


一打完就可以看到 iPhone Simulator 印出 server accept 字樣


 Socket listening on 0.0.0.0:5566
 server accept


接著隨意打一些字串,就可以看到 Simulator 印出接收到的字串以及把連線中斷的訊息,當然 client 也就中斷了。


上述粗體字是全域變數,可以加在 @interface 裡頭或是之接擺在 @implementation 後面也行。藍色部分是 Event 的設定,由於 socket handle 在系統底層也算是個 file handle,所以事件的偵測就跟偵測 file 變化類似,例如檔案開檔、新增資料和關檔等等。


參考資料:



2011年7月10日 星期日

第九位成員的誕生

工作之後,幾乎一年換一台電腦?真是噴錢噴真兇,只是說起來大概是 n 年前的主機已經不敷使用吧?仔細回想一下,家中第一台電腦是 586 等級,16MB 吧?硬碟 1.6 GB吧?有點忘了,但價錢都比第三台主機之後的貴;第二台是 P-200 ,我用他玩了太空戰士七等遊戲,還有 BBS 啦;第三台就隔得比較久,就是我升大二的 AMD XP 1800+ (大一下我還在用 P-200那台寫 C 語言交作業),也是第一次組裝電腦,感謝我的好友 Kudo 啦,而第一台跟第二台也就退役了;隔年第四台是 AMD XP 2200+ 組給求學的家人;第五台是買二手零件組了台 AMD 800 ,給家人上網;第六台算是努力工作的收穫, AMD SP 2600;第七台就是碩一時期,宿舍常跳電,讓我的主機掛了,這時起就沒換機殼了,那時還用第五台撐了會,最後則是兩台退役;工作後沒多久 AMD 2200+ 也掛了,第八台產生,這次就組得很好 XD 現在想起來還真的有些浪費,使用上並沒有那麼大的需求;第九台則是這次的新成員,也代表第六台的退役。往後大概會開始用筆電了。


這次的清單(不包含機殼):


處理器 CPU		AMD AM3 AthlonII X3-450三核/3.2G/95W/45nm/L2 Cache 512KB*3,AMD AM3 腳位
主機板 MB 華碩 M4A78LT-M LE/760G/DVI+RGB/最佳DDR3入門合板,AMD AM3/合板內建顯示
記憶體 RAM 兩隻,金士頓 DDR3 4G-1333單支,桌上型 DDR3-1333/1600單支
內接式硬碟 HDD WD 500G容量 SATA3 5000AAKX/16M/藍標/SATA3/三年保,最新一代 SATA3規格
燒錄器 CD/DVD/BD LITE-ON IHAS524/24XDVD燒錄/LabelTag功能可在光碟資料面寫上圖片及文字,終極DVD燒錄器 SATA介面
電源供應器 全漢 藍寶石二代 350W(PEAK 400)日系電容三年保/90天換新,全漢三年免費/POWER界霸主

設備再加上宅急便的運費、電話聯絡跟跨行轉帳費,不到 8900 元。若是想拿來當一台新主機,那大概要再加個 700~1000,多買個機殼吧。


第一次連燒錄器都買 SATA 介面,這是有風險的,例如作業系統中沒有支援 SATA 驅動程式(Windows XP?),可能導致無法讀取光碟機吧(不曉得透過 BIOS 將 SATA 弄成 IDE 模可否解決)?另外則是要擔心 Power 的 SATA 線夠不夠多,這次買的 Power 只有提供一條 SATA 電源線,上頭有兩個接頭,所以在安裝硬體上,會碰到光碟機跟硬碟的距離問題,必須確認兩台設備都可以接到電源線,這些問題又跟機殼有關。慶幸的,一切在舊機殼上都還順利完工了。


往後還是買這種中低階的零件就好了,CPU不用兩千五,而記憶體一條 4G-1333 才 8xx 元,也感謝主機板只有兩個記憶體插槽,不然我應該會噴更多錢吧 XD 唯一比較差的應該是硬碟只買 500 GB ,計算 CP 值並不划算,但用來組一台主機的基本零件,倒是還算夠用。這次組裝就比較留意,例如 CPU 跟主機板搭配時,是否有支援到記憶體的最大使用效率(DDR3-1333),還有 CPU 所需的瓦數主機板有沒支援(有的 AMD X3 或 X4 的 CPU 要 125W,這款主機板只有支援到 95W)。


就這樣,有了一台 3.2G 三核的 CPU 並可使用 8GB 記憶體,應該非常非常夠用了 :) 


F_AUDIO 接法 (前面板音效/前置音效)

F_AUDIO


回老家前,在某 3C 店家線上購物,剛剛好在我回到家之前送到,周末就著手組裝電腦及其相關的調教,於是碰到前面板音效的問題。新買的主機板是 ASUS M4A78LT-M LE ,此主機板支援 HD Audio 也支援 AC' 97 ,處理這個任務第一步就是要翻說明書啦,各家主機板可能都不一樣。


F_AUDIO manual
ASUS M4A78LT-M LE 說明書


F_AUDIO_NOTE
N 年前的 BBS 筆記


只是我家的機殼真的太舊了,其前置音效、USB的線都是拆開的一個 pin 腳一條,實在累人,所以翻了以前自己寫的 F_AUDIO 筆記,終於搞定了,結果測試的時候又發現前面麥克風沒聲音?最後才發現 BIOS 預設是 HD Audio 的,必須去 BIOS 裡設定


F_AUDIO_BIOS


我想,這類舊型機殼大概還是要淘汰掉才對,新的機殼都幫你把這些線包成一個大接頭,加上防呆針腳,基本上都不會再接錯,除此之外,想到舊主機板的前置 USB 還滿危險的,如果接錯的話,隨身碟一插就可能燒壞了,因此面對未知且老舊的主機,安全一點還是先接主機板上的 USB 孔吧。另外,用 "F_AUDIO 接法" 問 Google 可以看到別人寫的超詳細筆記,有需要可以參考看看:《教學》AC97和HD規範簡介和前置音效接法