Hexy Little Thing
February 14th, 2007I’m working on an application now that uses a custom document format. Since my code manipulates this format and spits it back out to disk, I find myself frequently examining the resulting documents using Peter Ammon’s excellent Hex Fiend to examine the resulting files, and make sure the content is still format-compliant.
But while I’m debugging the code that actually does the manipulation, I find myself stuck doing stupid stuff like:
(gdb) p/x ((char*)[imageData bytes])[14] $1 = 0x28
Ugh! That’s no way to win the software wars. It occurred to me today that what I really want is something like Hex Fiend built right into the debugger. Why shouldn’t I be able to hex-dump my NSData objects directly from the console? It turns out I wasn’t alone in this wish, because Dan Wood recently published a category on NSData that allows just that.
By simply compiling the NSData category into a given application, you give NSData’s debugging description superpowers. For instance, here’s me at the gdb console, examining the contents of the favicon.ico response I just received from NSURLConnection:
(gdb) po imageData
NSData 350 bytes:
00 00 01 00 01 00 10 10 10 00 00 00 00 00 28 01 | ..............(.
00 00 16 00 00 00 28 00 00 00 10 00 00 00 20 00 | ......(....... .
00 00 01 00 04 00 00 00 00 00 C0 00 00 00 00 00 | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF | ................
FF 00 00 00 FF 00 00 00 EF 00 00 00 DE 00 00 00 | ................
CE 00 00 00 BD 00 00 00 AD 00 00 00 9C 00 00 00 | ................
8C 00 00 00 7B 00 00 00 52 00 00 00 42 00 00 00 | ....{...R...B...
29 00 00 00 10 00 00 00 00 00 00 00 00 00 11 11 | )...............
11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 | ................
11 11 11 11 11 11 13 31 11 11 12 55 21 11 1B B1 | .......1...U!...
13 D8 7F FF FB 31 1B B1 6F D4 FB 65 9F D2 1B DB | ....1..o..e....
FB 21 84 11 15 F5 1B FF FC 41 12 78 8D D1 1B B1 | .!.......A.x....
3C C1 2D FF FB 51 1B B1 1A D1 5F 71 11 11 1B C5 | < .-..Q...._q....
BF 81 1C C7 58 61 1A FF C6 11 12 BF FF 81 12 54 | ....Xa.........T
11 11 11 13 52 11 11 11 11 11 11 11 11 11 11 11 | ....R...........
11 11 11 11 11 11 11 11 11 11 11 11 11 11 00 00 | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ..............
Current language: auto; currently objective-c
(gdb)
Dan’s code is pretty much perfect as-is, but I couldn’t resist tacking on some additional features. His category hardcodes the dumped data to 1024 bytes, but in my case there are times when I’m interested in seeing more. I added two category methods to the mix:
// startOffset may be negative, indicating offset from end of data
- (NSString *)descriptionFromOffset:(int)startOffset;
- (NSString *)descriptionFromOffset:(int)startOffset
limitingToByteCount:(unsigned int)maxBytes;
Now I can peek at a specific subrange, or expand the default limit of 1024 bytes. I can even peek at the end of a chunk of data, by specifying a negative offset:
(gdb) po [mDocumentData descriptionFromOffset:-11]
NSData 109 bytes (showing bytes 98 through 109):
02 43 41 54 41 54 4F 4E 49 43 00 | .CATATONIC.
(gdb)
Wow! This is a pretty huge addition to my debugging and development toolbelt. Many thanks to Dan Wood for providing the ready-made code to speed up my implementation.
NSData+RSHexDump is freely available to you, under an MIT license.
February 14th, 2007 at 12:27 pm
I think this post provides the answer to the question you posed in the previous one: yes, C is the new assembly. :)
February 14th, 2007 at 1:10 pm
Thanks for the pointer Daniel, this helps out a lot!
February 14th, 2007 at 3:10 pm
I did something similar in my work. I wrote a little function that dumps data from a file stream object into a simple buffer and then prints the buffer value to stdout.
I can call this function from the gdb command line then copy and paste the pointer value to the xcode memory browser to view the data.
The ultimate solution to the problem is for apple to beef up the memory browser in xcode and make it more useful and convenient to use. Why shouldn’t you be able to right click on an NSData instance and choose a menu item that brings up a memory browser for it. You should even be able to edit in the memory browser rather than haveing to drop to the gdb command line to manually change items in the buffer.
February 14th, 2007 at 3:25 pm
Why bloat your code like this? You should demand more from your debugger.
The MacsBug plugin for gdb has the “dm” command, which can be trivially added via gdb’s scripting language if you don’t want the entire plugin.
February 14th, 2007 at 3:32 pm
alexr: Extending that logic would lead one to question the use of – description methods in any classes. I am happy to trade the (quite minimal) code bloat for an improved debugging experience that fits in with the existing object-inspection pattern.
Even with the “dm” command, I’d have to conceptually thing “display memory” and coerce the NSData into an address, instead of using the same “po” habit that I use everywhere.
February 14th, 2007 at 8:18 pm
Thanks a lot for this great tip!
I just blogged about something similar. It’s about a project i started some weeks ago about improving the debugging experience in Xcode. You can read it at http://briksoftware.com/blog/wp-trackback.php?p=24
Karsten
February 14th, 2007 at 9:19 pm
alexr- I don’t have a mactel, but isn’t the MB plugin PPC-only?
February 15th, 2007 at 2:53 pm
Nice! (Though I’d probably rather use an NSRange for the second.)
March 3rd, 2007 at 1:43 pm
Hi Daniel, thanks – this is really useful.
I made a minor modification to display the concrete classname
and its address so one can distinguish different objects.
— NSData+RSHexDump.m 2007-02-14 19:47:59.000000000 +0100
+++ NSData+RSHexDump.m.bkr 2007-03-03 21:40:05.000000000 +0100
@@ -53,7 +53,8 @@
}
// Start the hexdump out with an overview of the content
– NSMutableString *buf = [NSMutableString stringWithFormat:@”NSData %d bytes%@:\n”, [self length], curtailInfo];
+ NSMutableString *buf = [NSMutableString stringWithFormat:@”%@(%p) %d bytes%@:\n”,
+ NSStringFromClass([self class]), self, [self length], curtailInfo];
// One row of 16-bytes at a time …
int i, j;