Over the weekend (and for some of the week) I've been working on back-porting block support from the Snow Leopard toolchain, with the goal of leveraging blocks for our iPhone and Mac OS X 10.5 development at Plausible Labs.
If you're unfamiliar with blocks, they're an implementation of closures for C, Objective-C, and provisionally C++. The full language specification and ABI specification are available from the LLVM/Clang project.
I've now published an initial beta release with support for iPhone OS 3.0 (armv6), and Mac OS X 10.5 (i386, x86-64, ppc) -- you can find the full announcement -- including download and usage instructions -- here.
12:39 Fri, 13 Feb 2009 PST -0800There's a lot of talk lately about preventing application piracy on the iPhone. Automated tools have been released to strip Apple's DRM, and a new anti-piracy product (which charges royalty fees on your sales) has been released.
As an iPhone developer, you have no access to the purchasing process. You can't issue (or revoke) serial numbers, implement an activation scheme, or provide any other fully independent copy protection. The only way to differentiate between a purchased copy of your application and a pirated one is to implement your own code to introspect the DRM that Apple has applied to your application.
On the phone, purchased applications are shipped to the user with a variety of meta-data that is readable by the application. The information potentially useful for implementing additional copy protection includes:
Using this information, it is possible to implement additional copy protection. The signature can be checked, the application encryption can be verified, etc. However, there's a problem -- none of this is documented by Apple. While most of the APIs and file formats are public, the actual distribution format is not. Apple could change the signature format, the meta-data plist, or any other distribution component at any time, at which point your copy protection may raise a false positive, and your paying customers will be wondering why you're wasting their time.
Given the risks, I've decided against implementing any further copy protection in my applications -- I believe it's Apple's problem to solve, and don't think it's worth risking false positives and annoyed customers. That said, if you'd like to take the plunge, here is some example code to get you started. For further reading, I suggest starting with Apple's Mac OS X Code Signing In Depth and Amit Singh's Understanding Apple's Binary Protection in Mac OS X.
The current process of cracking an application relies on stripping the application of encryption by attaching a debugger to the application on a jailbroken phone, dumping the text section containing the program code, and reinserting it into the original binary. The below code checks for the existence of LC_ENCRYPTION_INFO, and verifies that encryption is still enabled. There are, of course, a number of ways to defeat this check, but that's the nature of copy protection:
#import <dlfcn.h>
#import <mach-o/dyld.h>
#import <TargetConditionals.h>
/* The encryption info struct and constants are missing from the iPhoneSimulator SDK, but not from the iPhoneOS or
* Mac OS X SDKs. Since one doesn't ever ship a Simulator binary, we'll just provide the definitions here. */
#if TARGET_IPHONE_SIMULATOR && !defined(LC_ENCRYPTION_INFO)
#define LC_ENCRYPTION_INFO 0x21
struct encryption_info_command {
uint32_t cmd;
uint32_t cmdsize;
uint32_t cryptoff;
uint32_t cryptsize;
uint32_t cryptid;
};
#endif
int main (int argc, char *argv[]);
static BOOL is_encrypted () {
const struct mach_header *header;
Dl_info dlinfo;
/* Fetch the dlinfo for main() */
if (dladdr(main, &dlinfo) == 0 || dlinfo.dli_fbase == NULL) {
NSLog(@"Could not find main() symbol (very odd)");
return NO;
}
header = dlinfo.dli_fbase;
/* Compute the image size and search for a UUID */
struct load_command *cmd = (struct load_command *) (header+1);
for (uint32_t i = 0; cmd != NULL && i < header->ncmds; i++) {
/* Encryption info segment */
if (cmd->cmd == LC_ENCRYPTION_INFO) {
struct encryption_info_command *crypt_cmd = (struct encryption_info_command *) cmd;
/* Check if binary encryption is enabled */
if (crypt_cmd->cryptid < 1) {
/* Disabled, probably pirated */
return NO;
}
/* Probably not pirated? */
return YES;
}
cmd = (struct load_command *) ((uint8_t *) cmd + cmd->cmdsize);
}
/* Encryption info not found */
return NO;
}
16:41 Thu, 29 Jan 2009 PST -0800
Despite my best efforts to the contrary, I ship software with bugs.
After unit testing and integration testing, the bugs that tend to slip through are tricky ones -- race conditions, crashes triggered by bugs in platform vendor's implementation, and issues that only appear in specific configurations, such as a user synchronizing their iPhone's Address Book with Microsoft Outlook.
These are the types of issues that you hope to catch in beta testing. If you don't, however, these bugs leak into the wild.
On the iPhone, Apple generates crash logs for every third-party application crash. These plain text logs include backtraces, thread state, and other information to help you debug your crashes. Unfortunately, these crash logs are not actually readable by third party applications. As a software developer, you're reliant on users to report the bug (rather than, say, simply delete your application), and then at your behest, synchronize their iPhone, locate the (correct!) crash log on disk, and send it to you.
To solve this problem, I decided to implement our own Crash Reporter. It sports the following features:
If your application crashes, a crash report will be written. When the application is next run, you may check for a pending crash report, submit the report to your own HTTP server, send an e-mail, or even introspect the report locally. Additionally, I hope to add support for services like getexceptional to automatically handle uploading, notification, and tracking of crashing issues.
Crash logs are encoded using google protobuf, and may be decoded using the PLCrashReport API. Additionally, the included plcrashutil handles conversion of binary crash reports to the symbolicate-compatible iPhone text format.
/**
* Called to handle a pending crash report.
*/
- (void) handleCrashReport {
PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter];
NSData *crashData;
NSError *error;
/* Try loading the crash report */
crashData = [crashReporter loadPendingCrashReportDataAndReturnError: &error];
if (crashData == nil) {
NSLog(@"Could not load crash report: %@", error);
goto finish;
}
/* We could send the report from here, but we'll just print out
* some debugging info instead */
PLCrashReport *report = [[[PLCrashReport alloc] initWithData: crashData error: &error] autorelease];
if (report == nil) {
NSLog(@"Could not parse crash report");
goto finish;
}
NSLog(@"Crashed on %@", report.systemInfo.timestamp);
NSLog(@"Crashed with signal %@ (code %@, address=0x%" PRIx64 ")", report.signalInfo.name,
report.signalInfo.code, report.signalInfo.address);
/* Purge the report */
finish:
[crashReporter purgePendingCrashReport];
return;
}
// from UIApplicationDelegate protocol
- (void) applicationDidFinishLaunching: (UIApplication *) application {
PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter];
NSError *error;
/* Check if we previously crashed */
if ([crashReporter hasPendingCrashReport])
[self handleCrashReport];
/* Enable the Crash Reporter */
if (![crashReporter enableCrashReporterAndReturnError: &error])
NSLog(@"Warning: Could not enable crash reporter: %@", error);
...
}
The first beta release is now available as open source (MIT licensed) from the PLCrashReporter Project Page. This is intended for developer testing, and your feedback is most appreciated.
If you're interested in additional functionality, integration support, or other development services, feel free to drop me a line. We also gladly accept donations to support our open source development efforts: Donate via Paypal
12:57 Thu, 29 Jan 2009 PST -0800Plausible Labs' clone of Apple's CoverFlow™ is now available for off-the-shelf licensing.
You can also download a demonstration version of the library (limited to rendering every other cover) to try it out for yourself.
I would (perhaps unsurprisingly) perfer to release the library as open source, but R&D has to be funded! =)
21:14 Mon, 05 Jan 2009 PST -0800Within a few hours of its 1.0 release, our iPhone application was stripped of its DRM by a customer, and made available via http://appulo.us for use on jailbroken iPhones. Appulo.us serves as a comprehensive repository of pirated iPhone applications, with screen shots, application descriptions, and, of course, links to pirated copies.
The process of stripping DRM from iPhone applications has even been automated.
According to the Appulo.us FAQ, this software piracy serves as a solution to a "flawed app store". In providing pirated copies of our applications, Appulo.us claims that they are providing a justifiable service to our customers -- providing unlimited demos of our applications.
This justification is remarkably prevalent in the community of users that pirate software, as noted in James Bossert's recent blog post of his conversation with an iPhone software pirate:
When i crack an app, any app, i do not do it to hurt developers. Without you we wouldn’t even have our community =) I do this so people would know is an app worth their money.
I agree that Apple should allow demo applications -- users would be better served by the opportunity to test the application. However, this attempted justification does not hold water for one simple reason: as the copyright holder, I am perfectly capable of releasing a demo version of our application for jailbroken phones.
So I will. If you'd like to give Peeps a try on your jailbroken phone, you can download a demo .ipa or app. This version is identical to the latest release, but will display a "Please Purchase Peeps" dialog for 10 seconds when the application launches. (Note! You must have a jailbroken phone to run this Peeps Demo. Sorry!).
This is an experiment. If you like the application, please consider purchasing it. If you don't, let us know what you'd like to see improved. However, please don't distribute pirated versions of our software.
15:47 Wed, 24 Dec 2008 PST -0800An update to Safari Bookbag has been posted, using the CoverFlow implementation from Peeps to replace the previous UICoverFlowLayer implementation.
Apple appears to be getting more serious about checking for private APIs -- the previous 2.2 compatibility update to Safari Bookbag was rejected by Apple due to the continued use of UICoverFlowLayer.
On other platforms, I've found valgrind to be indispensable when it comes to discovering and fixing bugs. With Greg Parker's port of valgrind for Mac OS X now available, it's now possible to test your Mac and iPhone applications with valgrind.
Valgrind does not support ARM, but it is capable of executing i386 binaries built against the iPhone Simulator SDK. The only difficulty is that you can't run valgrind from the command line -- GUI iPhone Simulator binaries must be registered and run by Xcode, which uses the private iPhoneSimulatorRemoteClient framework to start and stop Simulator sessions.
To work around this, I wrote a small "re-exec" handler in my application's main(). It looks like this:
#define VALGRIND "/usr/local/valgrind/bin/valgrind"
int main(int argc, char *argv[]) {
#ifdef VALGRIND_REXEC
/* Using the valgrind build config, rexec ourself
* in valgrind */
if (argc < 2 || (argc >= 2 && strcmp(argv[1], "-valgrind") != 0)) {
execl(VALGRIND, VALGRIND, "--leak-check=full", argv[0], "-valgrind",
NULL);
}
#endif
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, @"PeepsAppDelegate");
[pool release];
return retVal;
}
To enable this code, I added a 'valgrind' build configuration to my project which defines VALGRIND_REXEC. After switching to the valgrind configuration, my application will run in the Simulator, under valgrind:
[Session started at 2008-12-24 15:27:47 -0800.] ==38596== Memcheck, a memory error detector. ==38596== Copyright (C) 2002-2008, and GNU GPL'd, by Julian Seward et al.
Running our code under valgrind has greatly facilitated the resolution of otherwise difficult to find or entirely unknown bugs.
11:29 Sun, 21 Dec 2008 PST -0800We're dancing over here. After quite a bumpy ride, Apple has approved Peeps for distribution in the App Store.
I even have a fancy image!
I'm already hard at work on the next version -- top requested features:
If you've got something you'd like on the list, send us an e-mail!
16:47 Fri, 12 Dec 2008 PST -0800Peeps has been approved!
After waiting 33 days to receive word on our app, Peeps, we've got a reply:
Upon review of your application, Peeps cannot be posted to the App Store due to the usage of a non-public API. Usage of non-public APIs, as outlined in the iPhone SDK Agreement section 3.3.1, is prohibited: "3.3.1 Applications may only use Published APIs in the manner prescribed by Apple and must not use or call any unpublished or private APIs. " The non-public API that is included in your application comes from the CoverFlow API set.
Let's be clear here: We did not use private API.
The last thing I would do is deliver time-bomb code to a paying customer. Private API can be broken or removed at any time by the vendor, and relying on it is unfair to your customers -- they rarely have any idea that the application they just purchased may not work next week, or next month.
So when I needed a CoverFlow-like user interface I wrote my own -- from scratch. I suppose I should be flattered that Apple mistook it for their own implementation (demo 1, demo 2).
In the mean time, I've got a support request in, and I'm waiting to hear back from the App Store. I don't fault Apple for the misunderstanding, I just wish they hadn't taken 33 days to tell me.
If you're willing to brave the App Store waters and would like to license our implementation for your own app, just say hello.
Update: You can now download a demo and purchase a copy of PLJukebox (our implementation) directly from the Plausible Labs website
20:30 Tue, 09 Dec 2008 PST -0800Peeps has been approved!
30 days ago, we were excited. Peeps 1.0 was finished, localized into a few languages, and submitted to the iPhone App Store for review. We even loved the icon — drawn by the talented Kelly J. Brownlee.
Most of my friend's apps were approved within a day, so after a week of waiting, I sent Apple an e-mail, to which they duly responded:
Your application Peeps is requiring unexpected additional time for review. We apologize for the delay, and will update you with further status as soon as we are able.
So we waited. An exercise in Zen — patience in the modern age. I released an update to the Plausible Database library. I finished preparing ActorKit for release. I took on some contracting work.
I sent follow-up e-mails, too. They all went unanswered. I even called Apple Developer Relations (they'll forward my query on, said the support representative. I should call for updates). So 30 days later, Peeps is still in limbo. It's not approved, nor is it rejected, it just simply is. I was fastidious in following Apple’s guidelines, used no private API, and I'm left with no idea what has triggered this state of application limbo.
What can I do? Apple doesn't answer my e-mails or phone calls, and my hard work is sitting in a queue, somewhere. I guess I write a blog post, and then try learn from this lesson in Zen.
[Addendum] - To clarify, we're not using the UICoverFlowLayer private API -- we wrote our own CoverFlow implementation (demo 1, demo 2)