PINE64のBROMのコードをダンプして逆アセンブルしてみた。
Pine64の続き。
記事はさっきあげましたが、昨日(2/26)Lチカを試したのですが、書きかけになっていたので公開していませんでした。
スタックポインタの設定の件とかモヤモヤしたので今日はBROMのコードをダンプしてみました。
有力な手がかり
http://linux-sunxi.org/Pine64#Boot_sequence
こちらのWiki、
The A64 SoC is wired to come out of reset in 32-bit monitor mode. As other Allwinner devices, the A64 SoC starts executing BROM code (mapped at address 0), which is consequently ARM32 code. The complete BROM code can be found at address 0x2c00 and has a total length of 32KB
という記載がありましたので0x2C00から32KB分のコードをダンプして逆アセンブルしてみました。
ダンプに使ったコード
typedef unsigned int u32; #define SUNXI_UART0_BASE 0x01C28000 #define SUNXI_PIO_BASE 0x01C20800 #define AW_CCM_BASE 0x01c20000 struct sunxi_gpio { u32 cfg[4]; u32 dat; u32 drv[2]; u32 pull[2]; }; struct sunxi_gpio_reg { struct sunxi_gpio gpio_bank[10]; }; #define GPIO_BANK(pin) ((pin) >> 5) #define GPIO_NUM(pin) ((pin) & 0x1F) #define GPIO_CFG_INDEX(pin) (((pin) & 0x1F) >> 3) #define GPIO_CFG_OFFSET(pin) ((((pin) & 0x1F) & 0x7) << 2) #define GPIO_PULL_INDEX(pin) (((pin) & 0x1f) >> 4) #define GPIO_PULL_OFFSET(pin) ((((pin) & 0x1f) & 0xf) << 1) /* SUNXI GPIO number definitions */ #define SUNXI_GPA(_nr) (0 + (_nr)) // banl 1 #define SUNXI_GPB(_nr) (0x20 + (_nr)) // bank 2(bank is 1~7, offset:0x20) #define SUNXI_GPIO_INPUT (0) #define SUNXI_GPIO_OUTPUT (1) #define SUN4I_GPB_UART0 (2) #define SUN50I_A64_GPB_UART0 (4) /* GPIO pin pull-up/down config */ #define SUNXI_GPIO_PULL_DISABLE (0) #define SUNXI_GPIO_PULL_UP (1) #define SUNXI_GPIO_PULL_DOWN (2) static void uart0_putc(char c); void puts_char2Hex(unsigned char cVal) { unsigned char tmp; unsigned char c; uart0_putc('0'); uart0_putc('x'); tmp = (cVal >> 4) & 0x0F; if (tmp < 0x0A) { // '0'~'9' c = 0x30 + tmp; } else { // 'A'~'F' 0x41 c = (0x37 + tmp); } uart0_putc(c); tmp = cVal & 0x0F; if (tmp < 0x0A) { // '0'~'9' c = 0x30 + tmp; } else { // 'A'~'F' 0x41 c = (0x37 + tmp); } uart0_putc(c); uart0_putc(','); } int sunxi_gpio_set_cfgpin(u32 pin, u32 val) { u32 cfg; u32 bank = GPIO_BANK(pin); u32 index = GPIO_CFG_INDEX(pin); u32 offset = GPIO_CFG_OFFSET(pin); struct sunxi_gpio *pio = &( (struct sunxi_gpio_reg *)SUNXI_PIO_BASE)->gpio_bank[bank]; cfg = *( (volatile unsigned long *)(&pio->cfg[0] + index)); cfg &= ~(0xf << offset); cfg |= val << offset; *(volatile unsigned long *)(&pio->cfg[0] + index) = (unsigned long)(cfg); return 0; } int sunxi_gpio_set_pull(u32 pin, u32 val) { u32 cfg; u32 bank = GPIO_BANK(pin); u32 index = GPIO_PULL_INDEX(pin); u32 offset = GPIO_PULL_OFFSET(pin); struct sunxi_gpio *pio = &((struct sunxi_gpio_reg *)SUNXI_PIO_BASE)->gpio_bank[bank]; cfg = *((volatile unsigned long *)(&pio->pull[0] + index)); cfg &= ~(0x3 << offset); cfg |= val << offset; *((volatile unsigned long *)(&pio->pull[0] + index)) = (unsigned long)(cfg); return 0; } int sunxi_gpio_output(u32 pin, u32 val) { u32 dat; u32 bank = GPIO_BANK(pin); u32 num = GPIO_NUM(pin); struct sunxi_gpio *pio = &((struct sunxi_gpio_reg *)SUNXI_PIO_BASE)->gpio_bank[bank]; dat = *((volatile unsigned long *)(&pio->dat)); if(val) dat |= 1 << num; else dat &= ~(1 << num); *(volatile unsigned long *)(&pio->dat) = (unsigned long)(dat); return 0; } int sunxi_gpio_input(u32 pin) { u32 dat; u32 bank = GPIO_BANK(pin); u32 num = GPIO_NUM(pin); struct sunxi_gpio *pio = &((struct sunxi_gpio_reg *)SUNXI_PIO_BASE)->gpio_bank[bank]; dat = *((volatile unsigned long *)(&pio->dat)); dat >>= num; return (dat & 0x1); } int gpio_direction_input(unsigned gpio) { sunxi_gpio_set_cfgpin(gpio, SUNXI_GPIO_INPUT); return sunxi_gpio_input(gpio); } int gpio_direction_output(unsigned gpio, int value) { sunxi_gpio_set_cfgpin(gpio, SUNXI_GPIO_OUTPUT); return sunxi_gpio_output(gpio, value); } /***************************************************************************** * UART is mostly the same on A10/A13/A20/A31/H3/A64, except that newer SoCs * * have changed the APB numbering scheme (A10/A13/A20 used to have APB0 and * * APB1 names, but newer SoCs just have renamed them into APB1 and APB2). * * The constants below are using the new APB numbering convention. * * Also the newer SoCs have introduced the APB2_RESET register, but writing * * to it effectively goes nowhere on older SoCs and is harmless. * *****************************************************************************/ #define CONFIG_CONS_INDEX 1 #define APB2_CFG (AW_CCM_BASE + 0x058) #define APB2_GATE (AW_CCM_BASE + 0x06C) #define APB2_RESET (AW_CCM_BASE + 0x2D8) #define APB2_GATE_UART_SHIFT (16) #define APB2_RESET_UART_SHIFT (16) void clock_init_uart(void) { /* Open the clock gate for UART0 */ *((volatile unsigned long *)(APB2_GATE)) |= (unsigned long)(1 << (APB2_GATE_UART_SHIFT + CONFIG_CONS_INDEX - 1)); /* Deassert UART0 reset (only needed on A31/A64/H3) */ *((volatile unsigned long *)(APB2_RESET)) |= (unsigned long)(1 << (APB2_RESET_UART_SHIFT + CONFIG_CONS_INDEX - 1)); } void gpio_init(void) { sunxi_gpio_set_cfgpin(SUNXI_GPB(8), SUN50I_A64_GPB_UART0); sunxi_gpio_set_cfgpin(SUNXI_GPB(9), SUN50I_A64_GPB_UART0); sunxi_gpio_set_pull(SUNXI_GPB(9), SUNXI_GPIO_PULL_UP); } /*****************************************************************************/ #define UART0_RBR (SUNXI_UART0_BASE + 0x0) /* receive buffer register */ #define UART0_THR (SUNXI_UART0_BASE + 0x0) /* transmit holding register */ #define UART0_DLL (SUNXI_UART0_BASE + 0x0) /* divisor latch low register */ #define UART0_DLH (SUNXI_UART0_BASE + 0x4) /* divisor latch high register */ #define UART0_IER (SUNXI_UART0_BASE + 0x4) /* interrupt enable reigster */ #define UART0_IIR (SUNXI_UART0_BASE + 0x8) /* interrupt identity register */ #define UART0_FCR (SUNXI_UART0_BASE + 0x8) /* fifo control register */ #define UART0_LCR (SUNXI_UART0_BASE + 0xc) /* line control register */ #define UART0_LSR (SUNXI_UART0_BASE + 0x14) /* line status register */ #define BAUD_115200 (0xD) /* 24 * 1000 * 1000 / 16 / 115200 = 13 */ #define NO_PARITY (0) #define ONE_STOP_BIT (0) #define DAT_LEN_8_BITS (3) #define LC_8_N_1 (NO_PARITY << 3 | ONE_STOP_BIT << 2 | DAT_LEN_8_BITS) void uart0_init(void) { clock_init_uart(); /* select dll dlh */ *(volatile unsigned long *)(UART0_LCR) = (unsigned long)(0x80); /* set baudrate */ *(volatile unsigned long *)(UART0_DLH) = (unsigned long)(0); *(volatile unsigned long *)(UART0_DLL) = (unsigned long)(BAUD_115200); /* set line control */ *(volatile unsigned long *)(UART0_LCR) = (unsigned long)(LC_8_N_1); } void uart0_putc(char c) { while (!(*((volatile unsigned long *)(UART0_LSR)) & (1 << 6))) {} *(volatile unsigned long *)(UART0_THR) = (unsigned long)(c); } void uart0_puts(const char *s) { while (*s) { if (*s == '\n') uart0_putc('\r'); uart0_putc(*s++); } } void __attribute__((section(".start"))) __attribute__((naked)) start(void) { asm volatile("b main \n" ".long 0xffffffff \n" ".long 0xffffffff \n" ".long 0xffffffff \n"); } // sudo dd if=./uart0-helloworld-sdboot.sunxi of=/dev/sdb bs=1024 seek=8 int main(void) { gpio_init(); uart0_init(); uart0_puts("Dump 32KB from 0x2c00.\n"); unsigned int addr = 0x2c00; unsigned char c = 0x00; for (addr = 0; addr < (0 + (1024 * 32)); addr++) { c = *(unsigned char *)addr; puts_char2Hex(c); } while (1); return 0; }
考察
逆アセンブルの結果をここで少し見てみたいと思います。
0: ea000007 b 0x24 4: ea000007 b 0x28 8: 4e4f4765 cdpmi 7, 4, cr4, cr15, cr5, {3} c: 4d52422e lfmmi f4, 2, [r2, #-184] ; 0xffffff48 10: 00000024 andeq r0, r0, r4, lsr #32 14: 30303131 eorscc r3, r0, r1, lsr r1 18: 30303131 eorscc r3, r0, r1, lsr r1 1c: 33333631 teqcc r3, #51380224 ; 0x3100000 20: 00000000 andeq r0, r0, r0 24: ea000000 b 0x2c 28: ea000001 b 0x34 2c: e3a06000 mov r6, #0 30: ea000003 b 0x44 34: e3a0605c mov r6, #92 ; 0x5c 38: ea00000e b 0x78 3c: e59f01e8 ldr r0, [pc, #488] ; 0x22c 40: e590f000 ldr pc, [r0] 44: ee100fb0 mrc 15, 0, r0, cr0, cr0, {5} 48: e2001003 and r1, r0, #3 4c: e3510000 cmp r1, #0 50: 1afffff9 bne 0x3c 54: e2001cff and r1, r0, #65280 ; 0xff00 58: e3510000 cmp r1, #0 5c: 1afffff6 bne 0x3c 60: e59f11c8 ldr r1, [pc, #456] ; 0x230 64: e59f21c8 ldr r2, [pc, #456] ; 0x234 68: e5913000 ldr r3, [r1] 6c: e1520003 cmp r2, r3 70: 1a000000 bne 0x78 74: eafffff0 b 0x3c 78: e3a00050 mov r0, #80 ; 0x50 7c: e2500001 subs r0, r0, #1 80: 1afffffd bne 0x7c 84: e10f0000 mrs r0, CPSR 88: e3c0001f bic r0, r0, #31 8c: e3800013 orr r0, r0, #19 90: e38000c0 orr r0, r0, #192 ; 0xc0 94: e3c00c02 bic r0, r0, #512 ; 0x200 98: e121f000 msr CPSR_c, r0 9c: ee110f10 mrc 15, 0, r0, cr1, cr0, {0} a0: e3c00005 bic r0, r0, #5 a4: e3c00b06 bic r0, r0, #6144 ; 0x1800 a8: ee010f10 mcr 15, 0, r0, cr1, cr0, {0} ac: e59f1184 ldr r1, [pc, #388] ; 0x238 b0: e5912000 ldr r2, [r1] b4: e3c22001 bic r2, r2, #1 b8: e5812000 str r2, [r1] bc: e59f1178 ldr r1, [pc, #376] ; 0x23c c0: e3a02801 mov r2, #65536 ; 0x10000 c4: e5913050 ldr r3, [r1, #80] ; 0x50 c8: e3c33803 bic r3, r3, #196608 ; 0x30000 cc: e1834002 orr r4, r3, r2 d0: e5814050 str r4, [r1, #80] ; 0x50 d4: e3a02000 mov r2, #0 d8: e5913050 ldr r3, [r1, #80] ; 0x50 dc: e3c33003 bic r3, r3, #3 e0: e1834002 orr r4, r3, r2 e4: e5814050 str r4, [r1, #80] ; 0x50 e8: e3a02c01 mov r2, #256 ; 0x100 ec: e5913054 ldr r3, [r1, #84] ; 0x54 f0: e3c330f0 bic r3, r3, #240 ; 0xf0 f4: e3c33c03 bic r3, r3, #768 ; 0x300 f8: e1834002 orr r4, r3, r2 fc: e5814054 str r4, [r1, #84] ; 0x54 100: e3a02a01 mov r2, #4096 ; 0x1000 104: e5913054 ldr r3, [r1, #84] ; 0x54 108: e3c33a03 bic r3, r3, #12288 ; 0x3000 10c: e1834002 orr r4, r3, r2 110: e5814054 str r4, [r1, #84] ; 0x54 114: e59f1120 ldr r1, [pc, #288] ; 0x23c 118: e59122c0 ldr r2, [r1, #704] ; 0x2c0 11c: e3a03040 mov r3, #64 ; 0x40 120: e1822003 orr r2, r2, r3 124: e58122c0 str r2, [r1, #704] ; 0x2c0 128: e5912060 ldr r2, [r1, #96] ; 0x60 12c: e3a03040 mov r3, #64 ; 0x40 130: e1822003 orr r2, r2, r3 134: e5812060 str r2, [r1, #96] ; 0x60 138: e5912068 ldr r2, [r1, #104] ; 0x68 13c: e3a03020 mov r3, #32 140: e1822003 orr r2, r2, r3 144: e5812068 str r2, [r1, #104] ; 0x68 148: e59fd0f0 ldr sp, [pc, #240] ; 0x240 14c: e59f30f0 ldr r3, [pc, #240] ; 0x244 150: e5932000 ldr r2, [r3] 154: e30f1fff movw r1, #65535 ; 0xffff 158: e0010002 and r0, r1, r2 15c: e30e1fe8 movw r1, #61416 ; 0xefe8 160: e1500001 cmp r0, r1 164: 0a00009e beq 0x3e4 168: e3a00e7d mov r0, #2000 ; 0x7d0 16c: e2500001 subs r0, r0, #1 170: 1afffffd bne 0x16c 174: e59fd0cc ldr sp, [pc, #204] ; 0x248 178: e3a01507 mov r1, #29360128 ; 0x1c00000 17c: e3a02000 mov r2, #0 180: e5812000 str r2, [r1] 184: e59f10c0 ldr r1, [pc, #192] ; 0x24c 188: e5912000 ldr r2, [r1] 18c: e3a03001 mov r3, #1 190: e1822003 orr r2, r2, r3 194: e5812000 str r2, [r1] 198: e59f10b0 ldr r1, [pc, #176] ; 0x250 19c: e5912000 ldr r2, [r1] 1a0: e3a03001 mov r3, #1 1a4: e1822003 orr r2, r2, r3 1a8: e5812000 str r2, [r1] 1ac: e356005c cmp r6, #92 ; 0x5c 1b0: 0a000042 beq 0x2c0 1b4: e3a0006f mov r0, #111 ; 0x6f 1b8: eb000047 bl 0x2dc 1bc: eafffffe b 0x1bc
ぱっと見た感じでは、24:の
24: ea000000 b 0x2c
からが実行コードのように見えるけど、いきなりb命令(無条件分岐)で始まり、しかもb命令が2回続けてでるとかは普通に考えるとありえない気がする。
あと、4c:と50:にはcmpからのbneがあるのでここは間違いなく実行コードだろう。
4c: e3510000 cmp r1, #0 50: 1afffff9 bne 0x3c
そう考えると、開始は24:で、何か理由があってb命令で始まっているのかもしれない。
いずれにせよ、気合入れて見てみないとどこで何しているか分からんなこれは。
やってみて分かったこと
ARMアセンブラの縦棒
ARMの機械語は必ず上位バイトがEになるという例の噂を実感できました。
実行命令はEになっていて、確かに条件分岐命令のところは、1 になっています。
こうすることでどこまで効率が上がるんだろう・・・
分岐かどうかの識別が早くなると、なんとなくパイプラインとか分岐予測とかいろんなところのパフォーマンスが地味に改善されそうな気がする。
生のバイナリの逆アセンブル
生のバイナリをダンプを直接逆アセンブルしてもデータセクションとかが含まれているときれいなアセンブラコードが出力されるとは限らない。逆アセンブルというのは、そこにコードがあるという保証がなければ意味がない可能性がある。
いや、当然といえば当然ですが。
いつもreadelfとかobjdumpを何も気にせず叩いているだけだったのでそんな根本的なことも理解できて来ませんでした。
ARMは固定長命令だからいいけど、可変長命令のCPUだとデータセクションが含まれていると恐らく解析すら不可能になる気がする・・・
そう意味では命令コードがどこにあるか分からないというのは脆弱性攻撃の対策にも多少なり有効な手段なんですね。
あと、ELF形式がいかに人類にやさしいか分かりました。