1 /*-
2 * Copyright (c) 2004 Sam Leffler, Errno Consulting
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer,
10 * without modification.
11 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12 * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13 * redistribution must be conditioned upon including a substantially
14 * similar Disclaimer requirement for further binary redistribution.
15 * 3. Neither the names of the above-listed copyright holders nor the names
16 * of any contributors may be used to endorse or promote products derived
17 * from this software without specific prior written permission.
18 *
19 * NO WARRANTY
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
23 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
24 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
25 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
28 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
30 * THE POSSIBILITY OF SUCH DAMAGES.
31 */
32
33 /*
34 * Simple tool for testing hardware/system crypto support.
35 *
36 * cryptotest [-czsbv] [-a algorithm] [count] [size ...]
37 *
38 * Run count iterations of a crypt+decrypt or mac operation on a buffer of
39 * size bytes. A random key and iv are used. Options:
40 * -c check the results
41 * -d dev pin work on device dev
42 * -z run all available algorithms on a variety of buffer sizes
43 * -v be verbose
44 * -b mark operations for batching
45 * -p profile kernel crypto operations (must be root)
46 * -t n fork n threads and run tests concurrently
47 * Known algorithms are:
48 * null null cbc
49 * des des cbc
50 * 3des 3des cbc
51 * blf blowfish cbc
52 * cast cast cbc
53 * skj skipjack cbc
54 * aes rijndael/aes 128-bit cbc
55 * aes192 rijndael/aes 192-bit cbc
56 * aes256 rijndael/aes 256-bit cbc
57 * chacha20 Chacha20 stream cipher
58 * blake2b Blake2b
59 * blake2s Blake2s
60 * md5 md5 hmac
61 * sha1 sha1 hmac
62 * sha256 256-bit sha2 hmac
63 * sha384 384-bit sha2 hmac
64 * sha512 512--bit sha2 hmac
65 *
66 * For a test of how fast a crypto card is, use something like:
67 * cryptotest -z 1024
68 * This will run a series of tests using the available crypto/cipher
69 * algorithms over a variety of buffer sizes. The 1024 says to do 1024
70 * iterations. Extra arguments can be used to specify one or more buffer
71 * sizes to use in doing tests.
72 *
73 * To fork multiple processes all doing the same work, specify -t X on the
74 * command line to get X "threads" running simultaneously. No effort is made
75 * to synchronize the threads or otherwise maximize load.
76 *
77 * If the kernel crypto code is built with CRYPTO_TIMING and you run as root,
78 * then you can specify the -p option to get a "profile" of the time spent
79 * processing crypto operations. At present this data is only meaningful for
80 * symmetric operations. To get meaningful numbers you must run on an idle
81 * machine.
82 *
83 * Expect ~400 Mb/s for a Broadcom 582x for 8K buffers on a reasonable CPU
84 * (64-bit PCI helps). Hifn 7811 parts top out at ~110 Mb/s.
85 */
86
87 #include <sys/param.h>
88 #include <sys/cpuset.h>
89 #include <sys/ioctl.h>
90 #include <sys/mman.h>
91 #include <sys/sysctl.h>
92 #include <sys/time.h>
93 #include <sys/wait.h>
94
95 #include <err.h>
96 #include <fcntl.h>
97 #include <paths.h>
98 #include <stdio.h>
99 #include <stdlib.h>
100 #include <string.h>
101 #include <sysexits.h>
102 #include <unistd.h>
103
104 #include <crypto/cryptodev.h>
105
106 #define CHUNK 64 /* how much to display */
107 #define streq(a,b) (strcasecmp(a,b) == 0)
108
109 void hexdump(char *, int);
110
111 int verbose = 0;
112 int opflags = 0;
113 int verify = 0;
114 int crid = CRYPTO_FLAG_HARDWARE;
115
116 struct alg {
117 const char* name;
118 int ishash;
119 int blocksize;
120 int minkeylen;
121 int maxkeylen;
122 int code;
123 } algorithms[] = {
124 #ifdef CRYPTO_NULL_CBC
125 { "null", 0, 8, 1, 256, CRYPTO_NULL_CBC },
126 #endif
127 { "des", 0, 8, 8, 8, CRYPTO_DES_CBC },
128 { "3des", 0, 8, 24, 24, CRYPTO_3DES_CBC },
129 { "blf", 0, 8, 5, 56, CRYPTO_BLF_CBC },
130 { "cast", 0, 8, 5, 16, CRYPTO_CAST_CBC },
131 { "skj", 0, 8, 10, 10, CRYPTO_SKIPJACK_CBC },
132 { "rij", 0, 16, 16, 16, CRYPTO_RIJNDAEL128_CBC},
133 { "aes", 0, 16, 16, 16, CRYPTO_AES_CBC},
134 { "aes192", 0, 16, 24, 24, CRYPTO_AES_CBC},
135 { "aes256", 0, 16, 32, 32, CRYPTO_AES_CBC},
136 { "chacha20", 0, 1, 32, 32, CRYPTO_CHACHA20},
137 { "blake2b", 1, 128, 64, 64, CRYPTO_BLAKE2B },
138 { "blake2s", 1, 64, 32, 32, CRYPTO_BLAKE2S },
139 { "md5", 1, 8, 16, 16, CRYPTO_MD5_HMAC },
140 { "sha1", 1, 8, 20, 20, CRYPTO_SHA1_HMAC },
141 { "sha256", 1, 8, 32, 32, CRYPTO_SHA2_256_HMAC },
142 { "sha384", 1, 8, 48, 48, CRYPTO_SHA2_384_HMAC },
143 { "sha512", 1, 8, 64, 64, CRYPTO_SHA2_512_HMAC },
144 };
145
146 void
usage(const char * cmd)147 usage(const char* cmd)
148 {
149 printf("usage: %s [-czsbv] [-d dev] [-a algorithm] [count] [size ...]\n",
150 cmd);
151 printf("where algorithm is one of:\n");
152 printf(" null des 3des (default) blowfish cast skipjack rij\n");
153 printf(" aes aes192 aes256 chacha20 md5 sha1 sha256 sha384 sha512\n");
154 printf(" blake2b blake2s\n");
155 printf(" or an encryption algorithm concatented with authentication\n");
156 printf(" algorithm with '+' in the middle, e.g., aes+sha1.\n");
157 printf("count is the number of encrypt/decrypt ops to do\n");
158 printf("size is the number of bytes of text to encrypt+decrypt\n");
159 printf("\n");
160 printf("-c check the results (slows timing)\n");
161 printf("-d use specific device, specify 'soft' for testing software implementations\n");
162 printf("\tNOTE: to use software you must set:\n\t sysctl kern.cryptodevallowsoft=1\n");
163 printf("-z run all available algorithms on a variety of sizes\n");
164 printf("-v be verbose\n");
165 printf("-b mark operations for batching\n");
166 printf("-p profile kernel crypto operation (must be root)\n");
167 printf("-t n for n threads and run tests concurrently\n");
168 exit(-1);
169 }
170
171 struct alg*
getalgbycode(int cipher)172 getalgbycode(int cipher)
173 {
174 int i;
175
176 for (i = 0; i < nitems(algorithms); i++)
177 if (cipher == algorithms[i].code)
178 return &algorithms[i];
179 return NULL;
180 }
181
182 struct alg*
getalgbyname(const char * name)183 getalgbyname(const char* name)
184 {
185 int i;
186
187 for (i = 0; i < nitems(algorithms); i++)
188 if (streq(name, algorithms[i].name))
189 return &algorithms[i];
190 return NULL;
191 }
192
193 int
devcrypto(void)194 devcrypto(void)
195 {
196 int fd = -1;
197
198 if (fd < 0) {
199 fd = open(_PATH_DEV "crypto", O_RDWR, 0);
200 if (fd < 0)
201 err(1, _PATH_DEV "crypto");
202 if (fcntl(fd, F_SETFD, 1) == -1)
203 err(1, "fcntl(F_SETFD) (devcrypto)");
204 }
205 return fd;
206 }
207
208 int
crlookup(const char * devname)209 crlookup(const char *devname)
210 {
211 struct crypt_find_op find;
212
213 if (strncmp(devname, "soft", 4) == 0)
214 return CRYPTO_FLAG_SOFTWARE;
215
216 find.crid = -1;
217 strlcpy(find.name, devname, sizeof(find.name));
218 if (ioctl(devcrypto(), CIOCFINDDEV, &find) == -1)
219 err(1, "ioctl(CIOCFINDDEV)");
220 return find.crid;
221 }
222
223 const char *
crfind(int crid)224 crfind(int crid)
225 {
226 static struct crypt_find_op find;
227
228 bzero(&find, sizeof(find));
229 find.crid = crid;
230 if (ioctl(devcrypto(), CIOCFINDDEV, &find) == -1)
231 err(1, "ioctl(CIOCFINDDEV): crid %d", crid);
232 return find.name;
233 }
234
235 char
rdigit(void)236 rdigit(void)
237 {
238 const char a[] = {
239 0x10,0x54,0x11,0x48,0x45,0x12,0x4f,0x13,0x49,0x53,0x14,0x41,
240 0x15,0x16,0x4e,0x55,0x54,0x17,0x18,0x4a,0x4f,0x42,0x19,0x01
241 };
242 return 0x20+a[random()%nitems(a)];
243 }
244
245 void
runtest(struct alg * ealg,struct alg * alg,int count,int size,u_long cmd,struct timeval * tv)246 runtest(struct alg *ealg, struct alg *alg, int count, int size, u_long cmd, struct timeval *tv)
247 {
248 int i, fd = devcrypto();
249 struct timeval start, stop, dt;
250 char *cleartext, *ciphertext, *originaltext, *key;
251 struct session2_op sop;
252 struct crypt_op cop;
253 char iv[EALG_MAX_BLOCK_LEN];
254 char digest[512/8];
255
256 /* Canonicalize 'ealg' to crypt alg and 'alg' to authentication alg. */
257 if (ealg == NULL && !alg->ishash) {
258 ealg = alg;
259 alg = NULL;
260 }
261
262 bzero(&sop, sizeof(sop));
263 if (ealg != NULL) {
264 sop.keylen = (ealg->minkeylen + ealg->maxkeylen)/2;
265 key = (char *) malloc(sop.keylen);
266 if (key == NULL)
267 err(1, "malloc (key)");
268 for (i = 0; i < sop.keylen; i++)
269 key[i] = rdigit();
270 sop.key = key;
271 sop.cipher = ealg->code;
272 }
273 if (alg != NULL) {
274 sop.mackeylen = (alg->minkeylen + alg->maxkeylen)/2;
275 key = (char *) malloc(sop.mackeylen);
276 if (key == NULL)
277 err(1, "malloc (mac)");
278 for (i = 0; i < sop.mackeylen; i++)
279 key[i] = rdigit();
280 sop.mackey = key;
281 sop.mac = alg->code;
282 }
283
284 sop.crid = crid;
285 if (ioctl(fd, cmd, &sop) < 0) {
286 if (cmd == CIOCGSESSION || cmd == CIOCGSESSION2) {
287 close(fd);
288 if (verbose) {
289 printf("cipher %s%s%s", ealg? ealg->name : "",
290 (ealg && alg) ? "+" : "",
291 alg? alg->name : "");
292
293 if (alg->ishash)
294 printf(" mackeylen %u\n", sop.mackeylen);
295 else
296 printf(" keylen %u\n", sop.keylen);
297 perror("CIOCGSESSION");
298 }
299 /* hardware doesn't support algorithm; skip it */
300 return;
301 }
302 printf("cipher %s%s%s keylen %u mackeylen %u\n",
303 ealg? ealg->name : "", (ealg && alg) ? "+" : "",
304 alg? alg->name : "", sop.keylen, sop.mackeylen);
305 err(1, "CIOCGSESSION");
306 }
307
308 originaltext = malloc(3*size);
309 if (originaltext == NULL)
310 err(1, "malloc (text)");
311 cleartext = originaltext+size;
312 ciphertext = cleartext+size;
313 for (i = 0; i < size; i++)
314 cleartext[i] = rdigit();
315 memcpy(originaltext, cleartext, size);
316 for (i = 0; i < nitems(iv); i++)
317 iv[i] = rdigit();
318
319 if (verbose) {
320 printf("session = 0x%x\n", sop.ses);
321 printf("device = %s\n", crfind(sop.crid));
322 printf("count = %d, size = %d\n", count, size);
323 if (ealg) {
324 printf("iv:");
325 hexdump(iv, sizeof iv);
326 }
327 printf("cleartext:");
328 hexdump(cleartext, MIN(size, CHUNK));
329 }
330
331 gettimeofday(&start, NULL);
332 if (ealg) {
333 for (i = 0; i < count; i++) {
334 cop.ses = sop.ses;
335 cop.op = COP_ENCRYPT;
336 cop.flags = opflags | COP_F_CIPHER_FIRST;
337 cop.len = size;
338 cop.src = cleartext;
339 cop.dst = ciphertext;
340 if (alg)
341 cop.mac = digest;
342 else
343 cop.mac = 0;
344 cop.iv = iv;
345
346 if (ioctl(fd, CIOCCRYPT, &cop) < 0)
347 err(1, "ioctl(CIOCCRYPT)");
348
349 if (verify && bcmp(ciphertext, cleartext, size) == 0) {
350 printf("cipher text unchanged:");
351 hexdump(ciphertext, size);
352 }
353
354 memset(cleartext, 'x', MIN(size, CHUNK));
355 cop.ses = sop.ses;
356 cop.op = COP_DECRYPT;
357 cop.flags = opflags;
358 cop.len = size;
359 cop.src = ciphertext;
360 cop.dst = cleartext;
361 if (alg)
362 cop.mac = digest;
363 else
364 cop.mac = 0;
365 cop.iv = iv;
366
367 if (ioctl(fd, CIOCCRYPT, &cop) < 0)
368 err(1, "ioctl(CIOCCRYPT)");
369
370 if (verify && bcmp(cleartext, originaltext, size) != 0) {
371 printf("decrypt mismatch:\n");
372 printf("original:");
373 hexdump(originaltext, size);
374 printf("cleartext:");
375 hexdump(cleartext, size);
376 }
377 }
378 } else {
379 for (i = 0; i < count; i++) {
380 cop.ses = sop.ses;
381 cop.op = 0;
382 cop.flags = opflags;
383 cop.len = size;
384 cop.src = cleartext;
385 cop.dst = 0;
386 cop.mac = ciphertext;
387 cop.iv = 0;
388
389 if (ioctl(fd, CIOCCRYPT, &cop) < 0)
390 err(1, "ioctl(CIOCCRYPT)");
391 }
392 }
393 gettimeofday(&stop, NULL);
394
395 if (ioctl(fd, CIOCFSESSION, &sop.ses) < 0)
396 perror("ioctl(CIOCFSESSION)");
397
398 if (verbose) {
399 printf("cleartext:");
400 hexdump(cleartext, MIN(size, CHUNK));
401 }
402 timersub(&stop, &start, tv);
403
404 free(originaltext);
405
406 close(fd);
407 }
408
409 #ifdef __FreeBSD__
410 void
resetstats()411 resetstats()
412 {
413 struct cryptostats stats;
414 size_t slen;
415
416 slen = sizeof (stats);
417 if (sysctlbyname("kern.crypto_stats", &stats, &slen, NULL, 0) < 0) {
418 perror("kern.crypto_stats");
419 return;
420 }
421 bzero(&stats.cs_invoke, sizeof (stats.cs_invoke));
422 bzero(&stats.cs_done, sizeof (stats.cs_done));
423 bzero(&stats.cs_cb, sizeof (stats.cs_cb));
424 bzero(&stats.cs_finis, sizeof (stats.cs_finis));
425 stats.cs_invoke.min.tv_sec = 10000;
426 stats.cs_done.min.tv_sec = 10000;
427 stats.cs_cb.min.tv_sec = 10000;
428 stats.cs_finis.min.tv_sec = 10000;
429 if (sysctlbyname("kern.crypto_stats", NULL, NULL, &stats, sizeof (stats)) < 0)
430 perror("kern.cryptostats");
431 }
432
433 void
printt(const char * tag,struct cryptotstat * ts)434 printt(const char* tag, struct cryptotstat *ts)
435 {
436 uint64_t avg, min, max;
437
438 if (ts->count == 0)
439 return;
440 avg = (1000000000LL*ts->acc.tv_sec + ts->acc.tv_nsec) / ts->count;
441 min = 1000000000LL*ts->min.tv_sec + ts->min.tv_nsec;
442 max = 1000000000LL*ts->max.tv_sec + ts->max.tv_nsec;
443 printf("%16.16s: avg %6llu ns : min %6llu ns : max %7llu ns [%u samps]\n",
444 tag, avg, min, max, ts->count);
445 }
446 #endif
447
448 void
runtests(struct alg * ealg,struct alg * alg,int count,int size,u_long cmd,int threads,int profile)449 runtests(struct alg *ealg, struct alg *alg, int count, int size, u_long cmd, int threads, int profile)
450 {
451 int i, status;
452 double t;
453 void *region;
454 struct timeval *tvp;
455 struct timeval total;
456 int otiming;
457
458 if (size % alg->blocksize || (ealg && size % ealg->blocksize)) {
459 if (verbose)
460 printf("skipping blocksize %u 'cuz not a multiple of "
461 "%s blocksize %u (or %s blocksize %u)\n",
462 size, alg->name, alg->blocksize,
463 ealg ? ealg->name : "n/a",
464 ealg ? ealg->blocksize : 0);
465 return;
466 }
467
468 region = mmap(NULL, threads * sizeof (struct timeval),
469 PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0);
470 if (region == MAP_FAILED) {
471 perror("mmap");
472 return;
473 }
474 tvp = (struct timeval *) region;
475 #ifdef __FreeBSD__
476 if (profile) {
477 size_t tlen = sizeof (otiming);
478 int timing = 1;
479
480 resetstats();
481 if (sysctlbyname("debug.crypto_timing", &otiming, &tlen,
482 &timing, sizeof (timing)) < 0)
483 perror("debug.crypto_timing");
484 }
485 #endif
486
487 if (threads > 1) {
488 for (i = 0; i < threads; i++)
489 if (fork() == 0) {
490 cpuset_t mask;
491 CPU_ZERO(&mask);
492 CPU_SET(i, &mask);
493 cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID,
494 -1, sizeof(mask), &mask);
495 runtest(ealg, alg, count, size, cmd, &tvp[i]);
496 exit(0);
497 }
498 while (waitpid(WAIT_MYPGRP, &status, 0) != -1)
499 ;
500 } else
501 runtest(ealg, alg, count, size, cmd, tvp);
502
503 t = 0;
504 for (i = 0; i < threads; i++)
505 t += (((double)tvp[i].tv_sec * 1000000 + tvp[i].tv_usec) / 1000000);
506 if (t) {
507 int nops = alg->ishash ? count : 2*count;
508
509 nops *= threads;
510 printf("%8.3lf sec, %7d %6s%s%6s crypts, %7d bytes, %8.0lf byte/sec, %7.1lf Mb/sec\n",
511 t, nops, alg->name, ealg? "+" : "", ealg? ealg->name : "",
512 size, (double)nops*size / t,
513 (double)nops*size / t * 8 / 1024 / 1024);
514 }
515 #ifdef __FreeBSD__
516 if (profile) {
517 struct cryptostats stats;
518 size_t slen = sizeof (stats);
519
520 if (sysctlbyname("debug.crypto_timing", NULL, NULL,
521 &otiming, sizeof (otiming)) < 0)
522 perror("debug.crypto_timing");
523 if (sysctlbyname("kern.crypto_stats", &stats, &slen, NULL, 0) < 0)
524 perror("kern.cryptostats");
525 if (stats.cs_invoke.count) {
526 printt("dispatch->invoke", &stats.cs_invoke);
527 printt("invoke->done", &stats.cs_done);
528 printt("done->cb", &stats.cs_cb);
529 printt("cb->finis", &stats.cs_finis);
530 }
531 }
532 #endif
533 fflush(stdout);
534 }
535
536 int
main(int argc,char ** argv)537 main(int argc, char **argv)
538 {
539 struct alg *alg = NULL, *ealg = NULL;
540 char *tmp;
541 int count = 1;
542 int sizes[128], nsizes = 0;
543 u_long cmd = CIOCGSESSION2;
544 int testall = 0;
545 int maxthreads = 1;
546 int profile = 0;
547 int i, ch;
548
549 while ((ch = getopt(argc, argv, "cpzsva:bd:t:")) != -1) {
550 switch (ch) {
551 #ifdef CIOCGSSESSION
552 case 's':
553 cmd = CIOCGSSESSION;
554 break;
555 #endif
556 case 'v':
557 verbose++;
558 break;
559 case 'a':
560 tmp = strchr(optarg, '+');
561 if (tmp != NULL) {
562 *tmp = '\0';
563 ealg = getalgbyname(optarg);
564 if (ealg == NULL || ealg->ishash)
565 usage(argv[0]);
566 optarg = tmp + 1;
567 }
568
569 alg = getalgbyname(optarg);
570 if (alg == NULL) {
571 if (streq(optarg, "rijndael"))
572 alg = getalgbyname("aes");
573 else
574 usage(argv[0]);
575 } else if (ealg != NULL && !alg->ishash)
576 usage(argv[0]);
577 break;
578 case 'd':
579 crid = crlookup(optarg);
580 break;
581 case 't':
582 maxthreads = atoi(optarg);
583 break;
584 case 'z':
585 testall = 1;
586 break;
587 case 'p':
588 profile = 1;
589 break;
590 case 'b':
591 opflags |= COP_F_BATCH;
592 break;
593 case 'c':
594 verify = 1;
595 break;
596 default:
597 usage(argv[0]);
598 }
599 }
600 argc -= optind, argv += optind;
601 if (argc > 0)
602 count = atoi(argv[0]);
603 while (argc > 1) {
604 int s = atoi(argv[1]);
605 if (nsizes < nitems(sizes)) {
606 sizes[nsizes++] = s;
607 } else {
608 printf("Too many sizes, ignoring %u\n", s);
609 }
610 argc--, argv++;
611 }
612 if (maxthreads > CPU_SETSIZE)
613 errx(EX_USAGE, "Too many threads, %d, choose fewer.", maxthreads);
614
615 if (nsizes == 0) {
616 if (alg)
617 sizes[nsizes++] = alg->blocksize;
618 else
619 sizes[nsizes++] = 8;
620 if (testall) {
621 while (sizes[nsizes-1] < 8*1024) {
622 sizes[nsizes] = sizes[nsizes-1]<<1;
623 nsizes++;
624 }
625 }
626 }
627
628 if (testall) {
629 for (i = 0; i < nitems(algorithms); i++) {
630 int j;
631 alg = &algorithms[i];
632 for (j = 0; j < nsizes; j++)
633 runtests(ealg, alg, count, sizes[j], cmd, maxthreads, profile);
634 }
635 } else {
636 if (alg == NULL)
637 alg = getalgbycode(CRYPTO_3DES_CBC);
638 for (i = 0; i < nsizes; i++)
639 runtests(ealg, alg, count, sizes[i], cmd, maxthreads, profile);
640 }
641
642 return (0);
643 }
644
645 void
hexdump(char * p,int n)646 hexdump(char *p, int n)
647 {
648 int i, off;
649
650 for (off = 0; n > 0; off += 16, n -= 16) {
651 printf("%s%04x:", off == 0 ? "\n" : "", off);
652 i = (n >= 16 ? 16 : n);
653 do {
654 printf(" %02x", *p++ & 0xff);
655 } while (--i);
656 printf("\n");
657 }
658 }
659