mcommit's message

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

ARMの命令セットの条件指定について

ARMの命令セット(v7)について調べていたので備忘録のためメモを残しておきます。

■ARMv7のマニュアル

https://www.macs.hw.ac.uk/~hwloidl/Courses/F28HS/Docu/DDI0406C_C_arm_architecture_reference_manual.pdf

とりあえず命令セットのところを読んでみる。
参考用に簡単な加算と条件分岐を含むコードを書いてみる。

■サンプルコード hoge.c

int add(void)
{
    int a = 0;
    a += 0x10;
    if ( a == 0x10) {
      return a;
    }
    return 0;
}


$ arm-linux-gnueabi-gcc -g -c hoge.c
$ arm-linux-gnueabi-objdump -S hoge.o


■サンプルアセンブラコード

00000000 <add>:
int add(void)
{
   0:   e52db004        push    {fp}            ; (str fp, [sp, #-4]!)
   4:   e28db000        add     fp, sp, #0
   8:   e24dd00c        sub     sp, sp, #12
    int a = 0;
   c:   e3a03000        mov     r3, #0
  10:   e50b3008        str     r3, [fp, #-8]
    a += 0x10;
  14:   e51b3008        ldr     r3, [fp, #-8]
  18:   e2833010        add     r3, r3, #16
  1c:   e50b3008        str     r3, [fp, #-8]
    if ( a == 0x10) {
  20:   e51b3008        ldr     r3, [fp, #-8]
  24:   e3530010        cmp     r3, #16
  28:   1a000001        bne     34 <add+0x34>
      return a;
  2c:   e51b3008        ldr     r3, [fp, #-8]
  30:   ea000000        b       38 <add+0x38>
    }
    return 0;
  34:   e3a03000        mov     r3, #0
}
  38:   e1a00003        mov     r0, r3
  3c:   e24bd000        sub     sp, fp, #0
  40:   e49db004        pop     {fp}            ; (ldr fp, [sp], #4)
  44:   e12fff1e        bx      lr

条件指定について

ARMの命令のうち、最上位の4bitは条件指定のビット(cond)として割り当てられているそうです。

f:id:simotin13:20180606010019p:plain

アセンブラを見るとこのcondビットはほぼほぼ1111(0xE..)になっていることが多い。
ここまでは過去の経験として知っていたのですが、このcondビットの意味がいまいちよく分かっていませんでした。※そういえばpine64のダンプとか読んだときも少し調べていました。

mcommit.hatenadiary.com

結論ですが、ARMでは(ほとんどの)命令に対して実行するかしないかの条件をつけることができるそうです。これだけだとピンとこないのですが、条件と言うのは要するにフラグレジスタの各種フラグビット(ゼロフラグとかキャリーフラグ...etc)に応じて実行するかしないかを命令に対して指定できるという意味になります。

実際に、上記のhoge.oの逆アセンブルですとbneだとcondは1になっています。
条件指定実行ができるということは
addとかmov命令をフラグレジスタの値を元に実行するかしないか切り替えれるというような意味だと思います。*1

というわけで実際にどの命令で条件指定が可能なのか、適当に試してみたいと思います。

が、今日眠いので続きは明日にします。

[2018/06/06 追記]

条件実行について更に調べてみた続きです。

mcommit.hatenadiary.com

*1:なんかrubyの後置記法みたいでおしゃれ

スタックの状態を可視化するWebツールを書いてみた。

Webページ上でStackの状態を可視化するコードを書いてみました。

Simple Stack Viewer

Stackを可視化するツール自体に目新しい感じはしないのですが、Web上でこういうツールってありそうでなかったので作ってみました。
かなりざっくりしたものですが、よかったら使ってください。

個人的なニーズとして普段Cとかアセンブラをガチでデバッグするとき、紙のノートにスタックの状態を書きながらデバッグしたりしているのですが、ノートだと動的にイメージし続けるのが辛いのであると便利な気がします。*1

課題

現状だと、pushするたびにページが下に伸びていくのでなんかしっくりこない気もするので、時間があるときに改善したいと思います。*2

その他

ついでにずっとほったらかしにしていたホームページも書き換えてみました。シンプルにindex.htmlだけにしてみました。

mcommit toppage

*1:GDBを使うとスタックとかレジスタの状態を見ることはできますが、いかんせんCUIなのが地味で直感的じゃない気がしていました

*2:もちろんpushはスタックトップに積まれて、popはスタックトップから取り出されていくのでご安心ください

構文解析ハンズオン 関西出張版に参加させて頂きました

lang-impl.connpass.com

参加させて頂きました。

良かった点

久しぶりにパーサ系のコード書きました。普段パーサ系のコードを書くことはあまりありませんが、時々必要に迫られるときがあるので定石やいろいろなテクニックや流行とか知っておくというのは大切な気がします。ハンズオンでいろいろ実際に書いてみるというのは自分にはぴったりでした。

あと、普段は再帰を使ってコードを書く機会が少ないので再帰を使うちょうどよい練習になりました。

苦労した点

Javaのコード書くのが久しぶりすぎてやばかった・・・
事前にJavaでハンズオンをするので環境を用意しておいてくださいと告知があったのですが、環境は構築していましたが、心と体の準備が追い付いていませんでした。
Javaはもう何年も書いていないけどまぁなんとかなるだろう」と思っていたら、コード書くのと実行するのに戸惑いました。やはり事前に復習をしておけばよかったように思います。

構文解析について思ったこと

パーサを書く場合に気を付けないといけないポイントとして、パースをする際の入出力と内部状態をしっかりと把握しておく必要があると思います。
入出力と状態は、

入力

  • 文字列

出力

  • 解析結果(AST)
  • 解析の成否(正常に解析できたのか、解析できなかったのか)

パーサ内部で保持する状態

  • 現在の解析位置
  • 入力文字列長

のような構成になります。

再帰下降的にパースする場合、各非終端記号に対応する関数は呼び出し元に

  • 解析結果(AST)
  • 解析の成否(正常に解析できたのか、解析できなかったのか)

を返す必要があります。

パーサを作る時に話がややこしくなるのは出力が複数存在するというのが1つの要因なのかなと思いました。
例えば戻り値をASTとした場合、解析の成否の情報は戻り値以外の方法で返す必要があります。

例外がサポートされている言語だと例外が1つの選択肢としてありますが、その場合例外クラスの設計をきちんと考えておく必要があります。
NULLやそれに類するものがある言語であれば戻り値のASTがNULLであれば解析異常とするのもありかなと思います。*1

Rubyで式の構文解析をしてみた

復習がてらハンズオンの課題の数式の解析コードを書いてみました。

gist.github.com

書いてみて気づいたこと

上記コードを書いていて気づいたのですが、入力が不正な場合の異常系に対応できていません。
非終端記号に対応する各関数は、解析異常時はnilを返すというポリシーで実装してみたのですが、再帰下降型で書くと想定していない文字が出てきたときに関数レベルではエラーハンドリングができないです。やはり字句解析フェーズがないというのはつらい気がする。
この辺は好みだと思いますが、個人的には先に入力を全てレキサーにかけるのが好きです。*2

再帰か状態遷移か

再帰を使わず状態遷移で書けば、適切に入力を弾けるので、仕様が小さくて手書きでパーサを書くのであれば状態遷移の方がいいようにも思いました。*3

再帰を使うメリットは何か考えてみたのですが、コードが短くなるというのはあるかも知れません。あと仕様が大きくなると状態遷移を考えるのがかえって面倒になる気がする。デバッグは状態遷移の方が断然しやすい気がしました。*4

今後やってみたいこと

パーサジェネレータを書く

パーサ系の実装についてはパーサジェネレータを書いてみたいです。*5
あまり難しくないはずですがBNFパーサを書かないといけないというところでいつも面倒くさくなって挫折してしまいます・・・あと、懇親会でPEGというものを教えてもらったので勉強してみると何か開けてきそうな気がしました。

関数型言語でパーサを書く

勉強会や懇親会でもイミュータブルとかパーサコンビネータの話題が挙がっていましたが、オブジェクト指向言語に囚われずに関数型の頭で何かパーサを書いてみたいです。

その他

ハンズオンの課題でJSONのパーサを書こうというのがありましたが、twitterで最近JSONのパーサに関して面白そうな論文が紹介されていました。

https://www.microsoft.com/en-us/research/publication/mison-fast-json-parser-data-analytics/

ざっと読んでみたところ

  1. JSONニーズ高まってて大きなデータ扱うことが増えてきたけど、パース遅いので何とかせねば。
  2. どうやってやるか→投機的解析と並列化で高速化してみた。
  3. やってみた結果→いい感じ。CSVとかHTMLとかXMLにも適用したい。

といった感じでしょうか。rustのプロジェクトが実装になるのかな。

github.com

解析する際にDBのようにインデックスをつけたり、SIMDとビット演算を使った並列化したりするらしい。
大量のデータとか扱う機会があれば、SIMDGPUFPGAあたりをデータの解析を目的として勉強してみたいです。

感想

勉強会も懇親会も色々と勉強になることが多かったです。

参加して思ったのは、構文解析を短い時間で「理解する、誰かに教える」というのは結構難しいのではないかと思いました。勉強会への参加だけでなく予習とか復習が欠かせない気がします。
あと、勉強会だと主催者の方や周りの詳しい方に質問できるので、やはりこういった場があるのはいいなぁと感じます。

kmizu様、チューターの皆様、いろいろ教えてくださった参加者の皆様、会場を使わせて頂いたはてな様、ありがとうございました。

*1:もしかしたら複数のオブジェクトを返せる言語(goとか!?)というのはパーサを書くのに向いているのかもしれない。Rubyでも複数返せるけど、そういう場合はなぜかHashを使いたくなる

*2:パーサに渡った時点で不正な文字が登場しないことが保証されるので

*3:組込だとスタックサイズの見積りの事情により再帰が使えない場合がある

*4:再帰のパースをデバッグする場合はログをしっかりと入れておかないとデバッグが難しいです

*5:せっかく勉強会に参加して気持ちが高ぶったので

Goでバイナリファイルを読み込んでHexダンプしてみた

最近触り始めたGo言語で、勉強としてバイナリファイルを読み込んでHexダンプしてみました。
バイナリファイルといってもfile.Readを使った読み込みなのでバイナリかどうかはあまり関係ないかもしれない。

package main

import (
  "fmt"
  "os"
)

func main() {
  argc := len(os.Args)
  if argc < 2 {
    fmt.Println("input file name.")
    return
  }

  filename := os.Args[1]
  f, err := os.Open(filename)
  if err != nil {
    fmt.Println("err:", err)
    return
  }

  c := make([]byte, 1)
  var buf []byte
  size := 0
  for {
    len, r_err := f.Read(c)
    if len == 0 {
      break
    }
    size += len
    if r_err != nil {
      fmt.Println("err:", err)
      return
    }
    buf = append(buf, c[0])
  }

  fmt.Printf("size:[%d]\n", size)
  for i := 0; i < size; i++ {
      fmt.Printf("%02X ", buf[i])
      if i % 16 == 15 {
        fmt.Println("")
      }
  }
}

感想

Go言語的な書き方がよくわからない

なんとなくC言語っぽく書いてみたけど、Go言語的に正しい書き方がよくわからない。
特にファイルから全データをリードする場合C言語だと大抵、EOFまで1byteずつreadするけどgoでもそんなのりでいいのかな・・・

配列とslice

RubyのArray的なのが使いたいけどgoだと配列とsliceを組み合わせて使うみたい。
sliceって機能と名前があってない気がする。

そもそも、goって名前自体英語だと普通の動詞になるので検索性がよくない。
例えば、for文について調べようとしたら

f:id:simotin13:20180326011052j:plain

みたいな結果になった。やはり名前重要。

参考

Go言語を勉強するにあたって「基礎からわかるGo言語」を手元に置いて読んでいます。

改訂2版 基礎からわかる Go言語

改訂2版 基礎からわかる Go言語

この書籍では前半で言語の基本的な機能について解説されていて、後半で逆引きサンプルが載っているのでかなり役に立っています。
今のところやりたいことに合わせて前半と後半をいったり来たりしながら読んでいます。

goらしい書き方についてはこの書籍だけでは掴めないので、go好きな人が書いたコードとかもっと読んだり写経した方がよさそう。

レジスタの値を取得するi386のアセンブラ関数を書いてみた

前回書いたPINE64+の続きとして、とりあえずBROMの処理が終わった状態でCPUがどうなっているか知りたいのですが、難航しています。

mcommit.hatenadiary.com

BROMのコードのダンプを見た結果、コプロセッサの設定とかをしていることは分かりましたがどうも追いきれないところもあるのでとりあえずCPUのレジスタの値をUARTでダンプ出力とかしてみたいと思いました。

レジスタを直接触るためにはアセンブラでコードを書く必要があり、少しハードルはあがります。
といってもARMのABIを理解して、出力したいレジスタからMOV命令で汎用レジスタにコピーするだけなのでそこまでは難しくはないはずです。

が、とりあえず練習もかねてi386で試してみました。

ABI的にポインタで渡す変数がスタックトップの次にあることを前提としています。関数側ではEBPの設定とかしないようにしたので、スタックトップはリターンアドレスになります。抜けるときもret命令だけで問題ありません。

reg.asm

section .text
global get_eax
global get_ecx
global get_edx
global get_ebx
global get_ebp
global get_esi
global get_edi
global get_esp
global get_eip

get_eax:
    mov ebx, [esp+0x04]
    mov [ebx], eax
    ret

get_ecx:
    mov eax, [esp+0x04]
    mov [eax], ecx
    ret

get_edx:
    mov eax, [esp+0x04]
    mov [eax], edx
    ret

get_ebx:
    mov eax, [esp+0x04]
    mov [eax], ebx
    ret

get_ebp:
    mov eax, [esp+0x04]
    mov [eax], ebp
    ret

get_esi:
    mov eax, [esp+0x04]
    mov [eax], esi
    ret

get_edi:
    mov eax, [esp+0x04]
    mov [eax], edi
    ret

get_esp:
    mov eax, [esp+0x04]
    mov [eax], esp
    sub [eax], dword 0x08
    ret

get_eip:
    mov eax, [esp]
    mov ebx, [esp+0x04]
    mov [ebx], eax	; return address on stack frame.
    ret

呼び出す側は、

main.c

#include <stdio.h>
#include <stdint.h>

extern void get_eax(uint32_t *reg);
extern void get_ecx(uint32_t *reg);
extern void get_edx(uint32_t *reg);
extern void get_ebx(uint32_t *reg);
extern void get_ebp(uint32_t *reg);
extern void get_esi(uint32_t *reg);
extern void get_edi(uint32_t *reg);
extern void get_esp(uint32_t *reg);
extern void get_eip(uint32_t *reg);

int main(int argc, char **argv)
{
    uint32_t eax, ecx, edx, ebx, esp, ebp, esi, edi, eip;

    get_eax(&eax);
    get_ecx(&ecx);
    get_edx(&edx);
    get_edx(&ebx);
    get_ebp(&ebp);
    get_esi(&esi);
    get_edi(&edi);
    get_esp(&esp);
    get_eip(&eip);

    printf("eax: [%08X]\n", eax);
    printf("ecx: [%08X]\n", ecx);
    printf("edx: [%08X]\n", edx);
    printf("ebx: [%08X]\n", ebx);
    printf("ebp: [%08X]\n", ebp);
    printf("esi: [%08X]\n", esi);
    printf("edi: [%08X]\n", edi);
    printf("esp: [%08X]\n", esp);
    printf("eip: [%08X]\n", eip);
    return 0;
}

のような感じで、ポインタ引数で受け取ることを想定した関数にしてみました。
※最初戻り値で作ってみたのですが、戻り値だとEAXが取れなくなってしまいます。
まだ全関数のデバッグはできていませんが、問題なく動いていそうです。

ビルドは、


$ nasm -f elf reg.asm
$ gcc main.c reg.o
のような感じです。nasmを使う場合は -f でフォーマットを指定する必要があります。

getがなんとなく書けたのでついでにsetも書きたい。それができたらARMv7版も書いてみよう。

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

PINE64をベアメタルで遊んでみる。

随分前にPine64+ 2GBというボードを買っていたのですが積み基板になっていました。
ちょっとARMで遊びたくなったのでベアメタルからのLチカしてみました。

ソース

とりあえずGithubで管理することにします。
github.com

ツールチェインについて

ロスコンパイラとイメージ作成用のツールが必要になります。


sudo apt install gcc-arm-none-eabi u-boot-tools

調べたこと

Pine64のブートについて

パワーオンリセットにより、BROM 内のプログラムから動き始めます。
Pine64はいくつかのブート方法に対応しています。
SDカードからブートする場合はセクタ16(オフセット8KB)の位置から32KB分読み込み、実行します。
この時点ではARM32bit命令で動作するそうです。

※要するに、BROMがBIOSとして機能し、SDカードのセクタ16がブートセクタになります。
注意点として、BROMはブートセクタの内容が適切かどうか、マジックナンバーとかチェックサムによって識別しているようです。

なので単純にARMの機械語のイメージがブートセクタに配置されていても正常に起動しません。
このため、コンパイル後、バイナリファイルに対してmksunxiboot コマンドによりブートセクタ用の変換を書ける必要があります。

LEDについて

いろいろ探してみたのですが、PINE64+には、ユーザーが制御できるオンボードのLEDは実装されていないようです。
回路図やネットの情報を見ると、PL7がLEDに割り当てられているようですが、このPL7は基板上ではホールになっており、ユーザによるオプション用のようです。

Lチカを試すにあたって、最初PL7をトグルさせてLチカを試そうとしたのですが、どうもうまく動きませんでした。
単純にIOアドレスとON/OFFの書き込みが間違っている可能性がありますが、何回か試してみてうまくいかなかったので
出力ポートをPB2にしてみて、トグル出力で無事Lチカできることを確認しました。


続き

BROMのコードが気になったのでダンプして逆アセンブルしてみました。まぁ、ただそれだけですが。
mcommit.hatenadiary.com