x86 で asm 書く機会ってほんとなくて、むしろ ARM の命令セットを学んだほうがいろいろモチベーションがでてきそう、ということで、とりあえず ARM Linux 上で簡単なのを書いてみます。

ARM

ARM は伝統的に OABI ("arm"), EABI ("armeb")という2つのインターフェイスがあるらしいです。OABI が古いほうで汎用 (Old)。EABI は組込み (Embeded) 用。ABI の違いということはシステムコールの呼びかたとか、サブルーチンの呼びかたが違いということです…… すなわちアセンブリで書くときはどっちを書いてるか明確にする必要があります。

ここでは EABI だけでやってみます。EABI の場合システムコールは、r7 にシステムコール番号を指定して、SVC 命令を発行 (オペランドは 0) すれば良いようです。

環境は GCC でやります。

その前に

アセンブリ書くとき、そもそも命令セット (instruction set) がわからないとどうにもならない。リストはARM のサイトから探す。

x86 とかも、とにかく命令セットがわからないと何もしようがない。x86 instruction set とかでググったらよさそう。x86 は命令いっぱいありすぎて pdf が4つぐらいにわかれてる。

ただ、GCC で書く場合 gas (gnu as) のフォーマットで書かないといけないので、データシートと形式がちがうのが罠い。

GCC as (gas) での書きかたは、公式のドキュメントを参照する必要がある。x86 と arm でも書きかたが違う。

exit するだけのコード

まず exit するだけのコードを書いてみます。

/*#!as -o sketch.o sketch.s && ld -o sketch -e _start sketch.o && ./sketch
 */

.global _start
_start:
        mov r0, $0x00 /* set exit status to 0 */
        mov r7, $0x01 /* set system call number to 1 (exit) */
        svc $0x00     /* supervisor call  */
$ as -o sketch.o sketch.s
$ ld -o sketch -e _start sketch.o
$ ./a.out

簡単!!!

r7 にシステムコール番号を入れ、r0 には exit の唯一の引数である終了ステータスを入れ、svc 命令でシステムコールが実行されます。当然 exit なので返ってきません。

obj-dump するとそのままのコードが出てきます

Disassembly of section .text:

00008054 <_start>:
    8054:       e3a00000        mov     r0, #0
    8058:       e3a07001        mov     r7, #1
    805c:       ef000000        svc     0x00000000

Hello, World

こっちは微妙に面倒です。普通に同じようにシステムコールを呼ぶだけですが、文字列をどこに置くか、どうやってアドレスを参照するかがいろいろあります。

.text に一緒に置く

たぶん一番シンプルな例

/* text section */

.section .text
.global _start

_start:
        mov r0, $0x01 /* set file descripter to 1 (stdout) */
        adr r1, msg   /* adr is GNU as's special instruction. This is translated to add */
        mov r2, $0x0d /* set msg length to 0x0d (13) */
        mov r7, $0x04 /* set system call number to 4 (write) */
        svc $0x00     /* supervisor call  */

        mov r0, $0x00 /* set exit status to 0 */
        mov r7, $0x01 /* set system call number to 1 (exit) */
        svc $0x00     /* supervisor call  */

msg:
        .asciz "Hello, World\n"
        .align 2

1行ごとにコメントつけてますが、この例では "Hello, World\n\0" という文字列が text セクションに置かれ、それを adr 命令で参照して r1 に書きこんでいます。adr 命令は GNU as が解釈して、指定したラベルを示すように add 命令に書きかえられます。試しにこれをアセンブルしたものを objdump すると

Disassembly of section .text:

00008054 <_start>:
    8054:       e3a00000        mov     r0, #0
    8058:       e28f1014        add     r1, pc, #20
    805c:       e3a0200d        mov     r2, #13
    8060:       e3a07004        mov     r7, #4
    8064:       ef000000        svc     0x00000000
    8068:       e3a00000        mov     r0, #0
    806c:       e3a07001        mov     r7, #1
    8070:       ef000000        svc     0x00000000

00008074 <msg>:
    8074:       6c6c6548        .word   0x6c6c6548
    8078:       57202c6f        .word   0x57202c6f
    807c:       646c726f        .word   0x646c726f
    8080:       000a            .short  0x000a
        ...

とかなってます。_start の中の adr があったところは add 命令に書きかわってます。.text はプログラム用のセクションですが、普通にデータも置けます。

.data セクションに置く

もう少しまともな例です。

/* text section */

.section .text
.global _start

_start:
        mov r0, $0x01   /* set file descripter to 1 (stdout) */
        ldr r1, msg     /* load memory address of msg to register  */
        ldr r2, msg_len /* load memory address of msg_len to register  */
        mov r7, $0x04   /* set system call number to 4 (write) */
        svc $0x00       /* supervisor call  */

        mov r0, $0x00 /* set exit status to 0 */
        mov r7, $0x01 /* set system call number to 1 (exit) */
        svc $0x00     /* supervisor call  */

msg:
        .word data_msg /* write data_msg address to here */
msg_len:
        .word data_msg_len

/* data section */
.section .data
data_msg:
        .asciz "Hello, World\n"
data_msg_len = . - data_msg
        .align 2

テキストデータ本体は data セクション (初期化される・書き換え可能・Cで書いた場合グローバル変数の領域) において、text セクションにはポインタのアドレスを置いています。ldr 命令でそのアドレスをレジスタの読みこみ、write を呼んでいます。

ついでに文字列の長さもアセンブラで計算させています。. は現在のアドレスを示していて、. から data_msg のアドレスを引くことで文字列の長さを求められます。

これを objdump -d -j .text -j .data sketch すると

Disassembly of section .text:

00008074 <_start>:
    8074:       e3a00000        mov     r0, #0
    8078:       e59f1014        ldr     r1, [pc, #20]   ; 8094 <msg>
    807c:       e59f2014        ldr     r2, [pc, #20]   ; 8098 <msg_len>
    8080:       e3a07004        mov     r7, #4
    8084:       ef000000        svc     0x00000000
    8088:       e3a00000        mov     r0, #0
    808c:       e3a07001        mov     r7, #1
    8090:       ef000000        svc     0x00000000

00008094 <msg>:
    8094:       0001009c        .word   0x0001009c

00008098 <msg_len>:
    8098:       0000000e        .word   0x0000000e

Disassembly of section .data:

0001009c <data_msg>:
   1009c:       6c6c6548        .word   0x6c6c6548
   100a0:       57202c6f        .word   0x57202c6f
   100a4:       646c726f        .word   0x646c726f
   100a8:       000a            .short  0x000a
        ...

みたいな感じになります。

ちなみに、ld に --verbose オプションをつけるとリンカスクリプトが表示されます。これはコードやデータを実際にメモリ上にどのように配置するかを定義したもので、ld はこの定義に従って、シンボルを解決 (アドレスを決めるということ) オブジェクトを配置します。

  1. トップ
  2. tech
  3. ARM アセンブリ Hello, World on Raspberry Pi

[tech] perl 5.19.9 の signatures 構文 (普通に引数を書ける構文) を試す | Mon, Feb 24. 2014 - 氾濫原 で「ただ引数の名前とかは外からとることができない。せっかく構文に組込まれたのなら、とれてもよさそうだなと思った」と書いたので、すこしだけ実装を書いてみた。

blead perl ecb4de39577c95734821743685366f6fe7f59a2d へのパッチです。

#!./perl -Ilib
use v5.19;
use strict;
use warnings;
use feature 'signatures';
no warnings "experimental::signatures";
use Test::More;
package signatures {
sub args ($sub) {
$signatures::subs{$sub+0};
}
sub arity ($sub) {
$signatures::arities{$sub+0};
}
}
subtest "signatures" => sub {
package _test1 {
sub foo ($xxx, $yyy) {
}
sub bar ($zzz, $aaa=1) {
}
sub baz ($zzz, @rest) {
}
}
is_deeply signatures::args(_test1->can('foo')), [qw(
$xxx
$yyy
)];
is signatures::arity(_test1->can('foo')), 2;
is_deeply signatures::args(_test1->can('bar')), [qw(
$zzz
$aaa
)];
is signatures::arity(_test1->can('bar')), 1;
is_deeply signatures::args(_test1->can('baz')), [qw(
$zzz
@rest
)];
is signatures::arity(_test1->can('baz')), -2;
};
subtest "no signature subs" => sub {
package _test2 {
sub foo {
}
}
is signatures::args(_test2->can('foo')), undef;
is signatures::arity(_test2->can('foo')), undef;
};
TODO: subtest 'lexical_subs' => sub {
local $TODO = "FAIL";
use feature 'lexical_subs';
no warnings "experimental::lexical_subs";
state sub foo ($foo) {
}
is_deeply signatures::args(\&foo), [qw(
$foo
)];
is signatures::arity(\&foo), 1;
};
done_testing;
view raw 01-signatures-args.t hosted with ❤ by GitHub
diff --git a/toke.c b/toke.c
index 88524b4..b517e37 100644
--- a/toke.c
+++ b/toke.c
@@ -12432,6 +12432,48 @@ S_parse_opt_lexvar(pTHX)
return var;
}
+void
+Perl_parse_subsignature_remember_sub_arg(pTHX_ OP* var)
+{
+ HV* signaturesubmap;
+ SV* subid;
+ SV* name;
+ HE* signatures;
+ AV* list;
+
+ signaturesubmap = get_hv("signatures::subs", 0);
+ if (!signaturesubmap) signaturesubmap = get_hv("signatures::subs", GV_ADD);
+
+ subid = newSViv((IV)(PL_compcv));
+ name = newSVsv(PAD_COMPNAME_SV(var->op_targ));
+ signatures = hv_fetch_ent(signaturesubmap, subid, 0, 0);
+ if (signatures) {
+ list = (AV*)SvRV(HeVAL(signatures));
+ } else {
+ list = newAV();
+ }
+ av_push(list, name);
+ hv_store_ent(signaturesubmap, subid, newRV_inc((SV*)list), 0);
+}
+
+void
+Perl_parse_subsignature_remember_sub_arity(pTHX_ int min_arity, int max_arity) {
+ HV* signaturearitymap;
+ SV* subid;
+ int arity;
+
+ signaturearitymap = get_hv("signatures::arities", 0);
+ if (!signaturearitymap) signaturearitymap = get_hv("signatures::arities", GV_ADD);
+
+ subid = newSViv((IV)(PL_compcv));
+ if (max_arity == -1) {
+ arity = -(min_arity + 1);
+ } else {
+ arity = min_arity;
+ }
+ hv_store_ent(signaturearitymap, subid, newSViv(arity), 0);
+}
+
OP *
Perl_parse_subsignature(pTHX)
{
@@ -12488,7 +12530,10 @@ Perl_parse_subsignature(pTHX)
prev_type = 0;
min_arity = pos + 1;
}
- if (var) expr = newASSIGNOP(OPf_STACKED, var, 0, expr);
+ if (var) {
+ expr = newASSIGNOP(OPf_STACKED, var, 0, expr);
+ Perl_parse_subsignature_remember_sub_arg(var);
+ }
if (expr)
initops = op_append_list(OP_LINESEQ, initops,
newSTATEOP(0, NULL, expr));
@@ -12539,6 +12584,7 @@ Perl_parse_subsignature(pTHX)
initops = op_append_list(OP_LINESEQ, initops,
newSTATEOP(0, NULL,
newASSIGNOP(OPf_STACKED, var, 0, slice)));
+ Perl_parse_subsignature_remember_sub_arg(var);
}
prev_type = 2;
max_arity = -1;
@@ -12590,6 +12636,7 @@ Perl_parse_subsignature(pTHX)
newSVpvs("Too many arguments for subroutine"))))),
initops);
}
+ Perl_parse_subsignature_remember_sub_arity(min_arity, max_arity);
return initops;
}

やってることは signatures::subs みたいな名前でハッシュをつくって放りこんでるだけです。perl のコード難しくてあってるかわからないのと、たぶん参照カウントがおかしかったりしてそうですが、とりあえず簡単には動く。

ただ、lexical_subs に対してはうまくとれない (とってるサブルーチンのアドレスがちがう) ので、そこが TODO かな、というのと、サブルーチンとか大量に定義されるので、結構メモリ食ったりしそうだな、ってのが怪しい感じです。

  1. トップ
  2. tech
  3. perl 5.19.9 の signatures 構文に引数リストをとれる機能を足す