mcommit's message

ソフトウェア開発の仕事をしているsimotinといいます。記事の内容でご質問やご意見がありましたらお気軽にコメントしてください\^o^/

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形式がいかに人類にやさしいか分かりました。