otool -L 相当のことをやる

Mach-O のフォーマットや Universal Binary のフォーマットを調べながら、otool -L 相当 (Linux でいうところの ldd) を自前でやってみた。
あ、ちなみに自分の cputype は i386 だと決めうちしてます。

#include <stdio.h>
#include <stdlib.h>
#include <mach-o/loader.h>
#include <mach-o/fat.h>

#define SWAP32(a) ((a)<<24|((a)&0x0000FF00)<<8|((a)&0x00FF0000)>>8|(a)>>24)

int main(int argc, char *argv[])
{
  uint32_t magic, mh_offset;
  FILE *fp;
  int i, j;
  struct fat_header fh;
  struct fat_arch fa;
  struct mach_header mh;
  struct load_command lc;
  struct dylib_command dc;

  for (i = 1; i < argc; i++) {
    printf("%s:\n", argv[i]);
    
    fp = fopen(argv[i], "r");
    if (fp == NULL) {
      perror("fopen");
      continue;
    }

    fread(&magic, 4, 1, fp);
    fseek(fp, 0, SEEK_SET);
    switch (magic) {
      case FAT_MAGIC:
      case FAT_CIGAM:
        fread(&fh, sizeof(struct fat_header), 1, fp);

        mh_offset = 0;
        if (magic == FAT_CIGAM) {
          fh.nfat_arch = SWAP32(fh.nfat_arch);
        }
        for (j = 0; j < fh.nfat_arch; j++) {
          fread(&fa, sizeof(struct fat_arch), 1, fp);
          if (magic == FAT_CIGAM) {
            fa.cputype = SWAP32(fa.cputype);
            fa.offset = SWAP32(fa.offset);
          }
          if (fa.cputype == CPU_TYPE_I386) {
            /* my cpu found */
            mh_offset = fa.offset;
            break;
          }
        }
        if (mh_offset == 0) {
          /* my cpu was not found */
          break;
        }
        fseek(fp, mh_offset, SEEK_SET);

        /* fall through */

      case MH_MAGIC:
        fread(&mh, sizeof(struct mach_header), 1, fp);
        if (mh.magic != MH_MAGIC) {
          puts("\tnot Mach-O file");
          break;
        }

        for (j = 0; j < mh.ncmds; j++) {
          fread(&lc, sizeof(struct load_command), 1, fp);
          fseek(fp, -sizeof(struct load_command), SEEK_CUR);

          if (lc.cmd == LC_LOAD_DYLIB) {
            fread(&dc, sizeof(struct dylib_command), 1, fp);
            size_t bufsize = lc.cmdsize - sizeof(struct dylib_command);
            char *buf = malloc(bufsize);
            fread(buf, 1, bufsize, fp);
            printf("\t%s (compatibility version %u.%u.%u, current version %u.%u.%u)\n",
                buf,
                (dc.dylib.compatibility_version & 0x00FF0000)>>16,
                (dc.dylib.compatibility_version & 0x0000FF00)>>8,
                dc.dylib.compatibility_version & 0x000000FF,
                (dc.dylib.current_version & 0x00FF0000)>>16,
                (dc.dylib.current_version & 0x0000FF00)>>8,
                dc.dylib.current_version & 0x000000FF
            );
            free(buf);
          }
          else {
            fseek(fp, lc.cmdsize, SEEK_CUR);
          }
        }
        break;

      default:
        puts("\tnot Mach-O file");
    }
    fclose(fp);
  }
  return 0;
}

使用例

$ ./a.out a.out =zsh /etc/passwd
a.out:
	/usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 111.1.1)
/bin/zsh:
	/usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 111.0.0)
	/usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)
	/usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)
/etc/passwd:
	not Mach-O file