bamboo’s blog

Bambooの気まぐれブログ

フォークしたリポジトリを同期する方法

はじめに

 リポジトリをフォークした後、フォーク元のリポジトリが更新された場合に同期する方法を紹介。

方法

①フォーク元のリポジトリupstreamという名前で追加する。

git remote add upstream (元のリポジトリのURL)


②フォーク元のリポジトリが追加されているか確認。

$ git remote -v

origin ~ (fetch)
origin ~ (push)
upstream ~ (fetch)
upstream ~ (push)


③フォーク元のリポジトリの内容を取り入れる。(以下はmasterブランチに取り入れる場合)

git pull upstream master


④変更内容をリモートリポジトリに反映する。

git push origin master


 これでフォーク元のリポジトリの変更内容を反映することができる。

Thumb命令とは(ARM)

はじめに

 Thumb命令とは、ARMプロセッサに組み込まれている16bit長の命令である。命令に制限はあるものの、ARM命令の半分の長さで実行できるため、ARM命令と組み合わせることで効率のよいコードを作成することができる。

Thumb関数の書き方

.thumb
.thumb_func
sample:
    add     r0, r0, r1
    bx      lr

 必要な疑似命令は2つ。.thumbは以降Thumbコードを生成しろという命令で、.thumb_funcは次の関数がThumbで書かれていることを示す宣言である。試しにこのコードを逆アセンブルしてみると以下のようになる。

00000000 <sample>:
       0:       1840            adds    r0, r0, r1
       2:       4770            bx      lr

 正しく16bitのThumb命令が生成されていることが分かる。ARM命令に戻す場合は、疑似命令.armを使用する。

切り替え方法

 ARMモードとThumbモードを切り替えるには、bx命令、またはblx命令を使用する。

blx命令

 こちらが一般的な切り替え方法。プログラムカウンタの値をリンクレジスタに保存した後、指定したラベルに分岐する。ラベル(相対アドレス)指定の場合、モードの切り替えは自動的に行われる。レジスタを使用して絶対アドレスで分岐する場合(blx ipなど)は、自動切り替えは行われないため注意。

.text
.global main

.thumb
.thumb_func
calc:
    add     r0, r0, r1
    bx      lr

.arm
main:
    push    {lr}
    mov     r0, #1
    mov     r1, #2
    blx     calc
    mov     r1, r0
    ldr     r0, =str
    bl      printf
    pop     {pc}

.data
str:    .asciz  "%d\n"

 上記の例では、引数2つを足して返す関数calcをThumbでコード生成するように指定している。リンクレジスタは分岐する際に上書きされるため、あらかじめスタックに退避しておく。

bx命令

 blx命令と同じく命令モードを切り替えられるが、いくつか異なる点がある。

 以上の点に注意。Thumbモードに切り替えるには、分岐先の絶対アドレスに1足した値を使用するレジスタにロードする。(最下位bitが1の時はThumbモードに、0の時はARMモードに遷移するという仕様である。)

.text
.global main

.thumb
.thumb_func
calc:
    add     r0, r0, r1
    bx      lr

.arm
main:
    push    {lr}
    mov     r0, #1
    mov     r1, #2
    ldr     ip, =calc
    mov     lr, pc
    bx      ip
    mov     r1, r0
    ldr     r0, =str
    bl      printf
    pop     {pc}

.data
str:    .asciz  "%d\n"

 上記の例で「ラベルに+1しなくていいの?」と不思議に思った方がいるかもしれないが、心配ない。.thumb_funcを宣言することで、でcalc関数がThumbの関数であるとアセンブラが解釈してくれるため、自動で+1される。ラベルならこれで大丈夫だが、絶対アドレスを直接ロードする際は+1することを忘れないようにしよう(例えば0x100000に分岐したい場合は0x100001をロードする)。

まとめ

 Thumb命令を積極的に使用することで、容量の削減や性能向上が期待できる。ぜひ活用してみよう。

ARM命令をハンドアセンブルしてみる

はじめに

 アセンブラがまだ発達していなかった頃、人間は機械語への翻訳をすべて手作業で行っていた。今となってはアセンブラやツールがかなり発達しているため、簡単に機械語に翻訳できる。とはいえ、アセンブリの命令と機械語がどのように対応しているかを知っておいて損はない。そこで今回は、命令を機械に頼らず機械語に組み立てるハンドアセンブルの方法を紹介する。今回は例として分岐命令を機械語に変換してみる。

プロセス

f:id:bamboo_cpu:20210107221448p:plain
ARMの分岐命令の構造

 ARMでの分岐命令の構造は上図のようになる。Condには条件コード、Lにはリンク付きにするかどうか、offsetにはプログラムカウンタからの相対アドレスを4で割った値を指定する。今回は試しに無限ループのコードを機械語に変換してみる。無限ループのコードはこうだ。

b #0

 このようにして実行中のアドレスに分岐することで、無限ループを実現できる。ここで注意しなければならないのは、プログラムカウンタ自体は実行中のアドレスの8バイト先を指しているということ。つまり、実行中のアドレスは8バイト手前。offsetにはこれを4で割った-2を指定する。

  1. まずはCondの指定。今回は常時実行にあたるAL(1110)を指定。
  2. 次にLの指定。リンク付きにしないので0
  3. -2を24bitで表わすと0xFFFFFE。この値をoffsetに指定する。


 以上を踏まえると機械語は次のようになる。

f:id:bamboo_cpu:20210107230947p:plain
`b #0` を機械語に変換した結果

 この変換が正しいかどうかは以下のサイトで確認できる。

armconverter.com

まとめ

今回はオプションが少なく機械語へ変換しやすい分岐命令を取り上げたが、ARMにはまだまだたくさんの命令がある。気になる方はARM Architecture Reference Manualをぜひ覗いてみてほしい。(Chapter A3 The ARM Instruction Setを参照)

ARMショートコーディング(コード短縮)技法

はじめに

 ショートコーディングとは、ソースコードをいかに短くできるかというものである。今回はARMアセンブリにおいて、どのようにすればコードを短くできるか、色々と考えてみる。

条件実行の短縮

mov r0, #0     @ r0 = 0
cmp r0, #0     @ if(r0 == 0)
beq <label>    @ goto <label>

 上記のようなコードがあったとする。「条件実行はcmp命令(論理演算であればtst命令)を使えばよい」と無条件に考える人は多いが、必ずしもそうではない。もちろん間違いではないが、ショートコーディングにおいては仇となる場合がある。

 もう少し詳しく見てみよう。cmp命令は内部的には以下のようなことを行っている。

1. 第1オペランドの値から第2オペランドの値を引く。
2. 演算結果をcpsr(フラグやプロセッサモードの情報が格納されているレジスタ)に反映する。

 例えば上記の例だとこうなる。

1. r0 - #0を計算。(r0には既に0が代入されているため、演算結果は0)
2. 演算結果が0になるため、Zフラグが設定される。
3. `eq`の実行はZフラグが設定されていることが条件であるため、Zフラグが設定されているとき`beq <label>`ではlabelへと分岐する。

 ここで本題へ戻る。上記の例は一見無駄がないように見えるかもしれない。ここで登場するのが、フラグフィールドを更新できる便利なサフィックスs。通常の命令の末尾にsをつけることで、演算結果をcpsrに反映できる。
 これを踏まえて上記のコードを書き直してみる。

movs r0, #0
beq <label>

 1行減らすことができた。movsを使用した時点で、movsの第一オペランドにある演算結果はcpsrに反映される。今回の演算結果は0であるため、わざわざcmp命令を使用しなくてもZフラグを設定することができる。
 今回の例ではたまたま演算結果が0であったため分岐に使用できたが、毎回そううまくいくとは限らない。また、cmp命令を省くとあるときに比べて読みづらくなる場合がある。あくまでショートコーディングの観点からすれば、こういう方法もある、ということである。

バレルシフタの活用

 バレルシフタとは、シフト処理を行うデジタル回路のことである。様々な命令に使用することができ、これを活用することでレジスタの値をシフト処理して使用したり、即値に指定できない値を指定したりすることができる。なお、バレルシフタが利用できる命令はムーブ命令(mov, mvn)、加算命令(add, adc)、減算命令(sub, sbc, rsb, rsc)、比較命令(cmp, cmn)、テスト命令(tst, teq)、論理演算命令(and, orr, eor, bic)である。

 例えば以下のコードは1命令で実行できる。

mov r0, #0x80000000

 「即値には8bitしか指定出来ないのでは?」と思った方がいるかもしれないが、これがバレルシフタのトリック。このコードはmov r0, #2, #2と同義で、「2を2bit右ローテートした結果(=0x80000000)をr0に代入」という意味になる。

 他にも、r1の4倍とr0を比較したいとき、

lsl r1, r1, #2    @r1を4倍
cmp r0, r1    @r0とr1を比較

と書くところを、

cmp r0, r1, lsl #2

とすれば1行減らすことができる。また、r1の値も書き換えずに済む。

アドレッシングモードの活用

 これはロード・ストア命令で使用することができる。以下にその種類を挙げる。

プリインデックス

 皆さんおなじみの記法。先に[]内を演算してメモリアドレスを算出し、そのメモリアドレスから値を取得する。

ldr r0, [r1, #4]    @r1+4の位置から取得した値をr0に格納

ライトバック付きプリインデックス

 ライトバック(!)を付けると、ベースアドレスを[]内の演算結果に書き換えることができる。

ldr r0, [r1, #4]!    @r1+4の位置から取得した値をr0に格納、r1にr1+4の値を格納

ポストインデックス

 こちらは先にベースアドレスをロードし、その後ベースアドレスを書き換える。

ldr r0, [r1], #4    @r1のメモリアドレスにある値をr0に格納、r1にr1+4の値を格納

 アドレッシングモードを活用すれば、通常ならadd命令などで加算しなければいけない部分を短縮することができる。

まとめ

 アセンブリで短いコードを書くことは、バイナリサイズの縮小や高速化、脆弱性のコード実証においても非常に重要である。日ごろから心がけるようにしよう。

GCCインラインアセンブラの使い方

 インラインアセンブラとは、ソースコード内にアセンブリコードを埋め込むことができるというコンパイラの機能である。本記事では、GCCにおけるインラインアセンブラの扱い方について解説する。

書き方

 インラインアセンブラは、asmまたは__asm__から始め、()の中にアセンブリコードを記述する。記述方法は以下の通り。

asm(
    "" // アセンブリコード
    : // 出力オペランド(オプション)
    : // 入力オペランド(オプション)
    : // 上書きレジスタ(オプション)
)

アセンブリコード

アセンブリコードを記述する。複数のコードを記述する場合はセミコロン;で区切る。

// 例
asm(
    "mov r0, #1;"
    "mov r1, #2"
)

入出力オペランド

 出力オペランドを指定すると、C言語の変数に値を渡すことができる。入力オペランドを指定すると、C言語の変数から値を受け取ることができる。記述方法はどちらも同じである。複数記述する場合はコンマ,で区切る。

asm(
    "" // アセンブリコード
    : [マクロ名] "オペランド制約" (変数名)
)

 オペランド制約では値をどのように扱うかを決定する。出力オペランドには追加で制約修飾子を指定する。主に使用されるのは以下の通り。

オペランド制約 意味
r 汎用レジスタ(r0~r15)
I 即値
m メモリアドレス
制約修飾子 意味
= 書き込みのみ
+ 読み書き可

上書きレジスタ

 インラインアセンブラを使うと任意の場所でレジスタを上書きできるが、そのまま上書きすると予期せぬ動作を起こす可能性がある。レジスタを上書きする場合は、このセクションであらかじめ上書きするレジスタを指定しておくと良い。

コード例

 以上を踏まえて、インラインアセンブラを使用した演算例を紹介。

#include <stdio.h>

int main(void) {

    int a = 1, b = 2, sum;

    asm(
        "add %[Rd], %[Rs1], %[Rs2]" // Rd = Rs1 + Rs2
        : [Rd] "=r" (sum) // Rdをsumに出力
        : [Rs1] "r" (a), [Rs2] "r" (b) // aをRs1に、bをRs2に入力
    );

    printf("%d + %d = %d\n", a, b, sum); // a + b = sum

    return 0;
}

 基本的な処理はCで記述しているが、演算処理はアセンブリコードで記述している。これをARM用にビルドし実行すると1 + 2 = 3と表示されるはずだ。上記の例ではレジスタを使用していないが、レジスタを使用する場合は上書きレジスタを指定するのを忘れずに。

連長圧縮について

圧縮の基本ともいえる連長圧縮についておさらい。

連長圧縮とは

Wikipediaより引用。

連長圧縮(れんちょうあっしゅく)は、データ圧縮アルゴリズムの一つで、可逆圧縮に分類される。ランレングス圧縮、RLE (Run Length Encoding) とも呼ばれる。

アルゴリズム

連長圧縮では、連続するデータをデータ1つとそのサイズに置き換える。

AAABBCCCC ⇒ A3B2C4

上記の例の場合、データのサイズが9から6に減少しているため、約66%(2/3)圧縮できていることになる。

欠点

例えばこの法則を次のような例に適用するとどうなるだろうか。

ABCDEF

この場合、「サイズ1のデータが6個ある」と解釈されるため、A1B1C1D1E1F1となる。
しかしこれでは圧縮の意味を成さない。この場合だと、サイズは元のデータの2倍に膨れ上がってしまっている。

解決法

PackBitsという手法を利用する。PackBitsでは、連続しないデータが見つかった場合、連続するデータが表れるまでの長さを記録する。

  • 圧縮前:AAABBCCCCDEFG
  • 圧縮後:3A2B4C-4DEFG

正数で連続するデータの長さを、負数で連続しないデータの長さを表すことによって、連続しているデータかどうかを区別する。これで連長圧縮が持つ問題を回避することができる。

WSLのUbuntuでllamaをビルド

llama3DSのfirmエミュレータ。ビルドするためにはいくつかパッケージをインストールする必要があったためメモ。

手順

まずはパッケージの更新。

sudo apt update && sudo apt upgrade -y

続いて必要なパッケージをインストール。

sudo apt install cargo pkg-config cmake build-essential qt5-default qtdeclarative5-dev clang

これで正常にビルドできる。