2014/10/20

static library and shared library

1. Static library


static library 把許多 object 檔包成同一個檔案, 用 ar 指令打包
在 link 的時候, 會在 library 裡找到對應的 object 副本, 再作 link 的動作
它以 object 檔為單位, link 的時候會放一份副本
也因此如果有多個執行檔都用到同個 static library, 每個執行檔都會有一份副本
使用的記憶體也因為每個執行檔都有一份, 所以也使用較多的記憶體
在更新 static library 時, 也得要將所有用到這份 static library 的執行檔都重新 link

2. Shared library


shared library 透過 mmap 的機制讓多個 process 共享記憶體, 並參考其中內容
一樣是將多個 object 打包, OS 會參考 memory mapping table , 等到實際取用的時候才去讀取資料

因為多個執行檔只要一份, 所以較省空間與記憶體
更新 shared library 也只需要更新一次

製作 shared library:
$ gcc -c foo.c
$ gcc -c bar.c
$ gcc -shared -W1,-soname,libfoo.so.o -o libfoo.so foo.o bar.o
-shared 表示要建立 shared object
-soname 表示 shared object soname, (別名)

使用的方式:
$ gcc -o baz baz.o -lfoo

3. Dependency of shared library


ELF 的 dynamic section 的 NEEDED 裡會儲存檔案用到哪些 shared library, 可以用objdump來看
$ objdump -p /bin/ls

/bin/ls:     file format elf32-littlearm

Program Header:
......

Dynamic Section:
  NEEDED               libselinux.so.1
  NEEDED               librt.so.1
  NEEDED               libacl.so.1
  NEEDED               libgcc_s.so.1
  NEEDED               libc.so.6
  NEEDED               ld-linux-armhf.so.3
  INIT                 0x00009bb8

或是用readelf
$ readelf -d /bin/ls

Dynamic section at offset 0x16ee8 contains 30 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libselinux.so.1]
 0x00000001 (NEEDED)                     Shared library: [librt.so.1]
 0x00000001 (NEEDED)                     Shared library: [libacl.so.1]
 0x00000001 (NEEDED)                     Shared library: [libgcc_s.so.1]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x00000001 (NEEDED)                     Shared library: [ld-linux-armhf.so.3]
 0x0000000c (INIT)                       0x9bb8

NEEDED 記錄的是 soname, 為了要找到 soname 對應的實際的檔案, 底下是可能的找法

  • 它會先從 /usr/lib 找 soname的檔案
  • 如果環境變數 LD_LIBRARY_PAT 有設定, 就會在設定的路徑下找
  • 如果 /etc/ld.so.cache 有記錄相關資訊的話, 也會使用它的內容

一個一個找其實很麻煩, 通常會使用 ldd 來檢查
$ ldd /bin/ls
        /usr/lib/arm-linux-gnueabihf/libcofi_rpi.so (0xb6f10000)
        libselinux.so.1 => /lib/arm-linux-gnueabihf/libselinux.so.1 (0xb6edf000)
        librt.so.1 => /lib/arm-linux-gnueabihf/librt.so.1 (0xb6ed0000)
        libacl.so.1 => /lib/arm-linux-gnueabihf/libacl.so.1 (0xb6ec1000)
        libgcc_s.so.1 => /lib/arm-linux-gnueabihf/libgcc_s.so.1 (0xb6e99000)
        libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0xb6d69000)
        /lib/ld-linux-armhf.so.3 (0xb6f1e000)
        libdl.so.2 => /lib/arm-linux-gnueabihf/libdl.so.2 (0xb6d5e000)
        libpthread.so.0 => /lib/arm-linux-gnueabihf/libpthread.so.0 (0xb6d3f000)
        libattr.so.1 => /lib/arm-linux-gnueabihf/libattr.so.1 (0xb6d32000)

它會顯示出所有需要的 shared library, soname, 以及實際的位置











2014/10/13

ELF (Executable and Linking Format)

1. Abstract


ELF (Executable and Linking Format), 是 executable binary file 以及 object file 的檔案格式規範

ELF 的檔案開頭會有 (1) ELF header, (2) program header table, (3) section header table

ELF 相關的 define 在 "/usr/include/elf.h", 裡面有分 32-bit 及 64-bit, 以下都以32-bit當例子

2. ELF header


查看 ELF header 的內容, 可以用 readelf
$ readelf -h /bin/ls
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0xc3f0
  Start of program headers:          52 (bytes into file)
  Start of section headers:          95220 (bytes into file)
  Flags:                             0x5000002, has entry point, Version5 EABI
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         9
  Size of section headers:           40 (bytes)
  Number of section headers:         28
  Section header string table index: 27

其實這只是把 /bin/ls 的 binary 的開頭部份以 Elf32_Ehdr 來 parse
Elf32_Ehdr 在 "/usr/include/elf.h" 的定義為
typedef struct
{
  unsigned char e_ident[EI_NIDENT];     /* Magic number and other info */
  Elf32_Half    e_type;                 /* Object file type */
  Elf32_Half    e_machine;              /* Architecture */
  Elf32_Word    e_version;              /* Object file version */
  Elf32_Addr    e_entry;                /* Entry point virtual address */
  Elf32_Off     e_phoff;                /* Program header table file offset */
  Elf32_Off     e_shoff;                /* Section header table file offset */
  Elf32_Word    e_flags;                /* Processor-specific flags */
  Elf32_Half    e_ehsize;               /* ELF header size in bytes */
  Elf32_Half    e_phentsize;            /* Program header table entry size */
  Elf32_Half    e_phnum;                /* Program header table entry count */
  Elf32_Half    e_shentsize;            /* Section header table entry size */
  Elf32_Half    e_shnum;                /* Section header table entry count */
  Elf32_Half    e_shstrndx;             /* Section header string table index */
} Elf32_Ehdr;

那麼把 /bin/ls 的開頭 dump 出來就可以對應到以上的欄位
$ hexdump -n 64 -C /bin/ls
00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 28 00 01 00 00 00  f0 c3 00 00 34 00 00 00  |..(.........4...|
00000020  f4 73 01 00 02 00 00 05  34 00 20 00 09 00 28 00  |.s......4. ...(.|
00000030  1c 00 1b 00 01 00 00 70  c0 60 01 00 c0 e0 01 00  |.......p.`......|

其中 Elf32_Ehdr 實際對應的值是
e_ident:     7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00
e_type:      02 00       = 0x0002
e_machine:   28 00       = 0x0028
e_version:   01 00 00 00 = 0x00000001
e_entry:     f0 c3 00 00 = 0x0000c3f0
e_phoff:     34 00 00 00 = 0x00000034 = 52 (bytes)
e_shoff:     f4 73 01 00 = 0x000173f4 = 95220 (bytes)
e_flags:     02 00 00 05
e_ehsize:    34 00       = 0x0034     = 52 (bytes)
e_phentsize: 20 00       = 0x0020     = 32 (bytes)
e_phnum:     09 00       = 0x0009
e_shentsize: 28 00       = 0x0028
e_shnum:     1c 00       = 0x001c
e_shstrndx:  1b 00       = 0x001b

其中 e_ident 包含 ELF magic number 和其它資訊, ELF 檔案的前 4 個byte一定是 "0x7f 45 4c 46"

e_type 則是底下幾種:

  • ET_REL (1) : relocatable file
  • ET_EXEC (2) : executable file
  • ET_DYN (3) : shared object file
  • ET_CORE (4) : core file

3. Program Header


program header table的位置從 ELF header 的 "Start of program headers" (e_phoff) 開始
program header 的大小則是由 "Size of program headers" (e_phentsize) 以及 "Number of program headers" (e_phnum) 決定

"/bin/ls" 的例子裡, program header 從offset 52 bytes 開始, header size是 32bytes, 共有 9 個header

用 readelf 來解讀:
$ readelf --program-headers /bin/ls

Elf file type is EXEC (Executable file)
Entry point 0xc3f0
There are 9 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  EXIDX          0x0160c0 0x0001e0c0 0x0001e0c0 0x00028 0x00028 R   0x4
  PHDR           0x000034 0x00008034 0x00008034 0x00120 0x00120 R E 0x4
  INTERP         0x000154 0x00008154 0x00008154 0x00019 0x00019 R   0x1
      [Requesting program interpreter: /lib/ld-linux-armhf.so.3]
  LOAD           0x000000 0x00008000 0x00008000 0x160ec 0x160ec R E 0x8000
  LOAD           0x016edc 0x00026edc 0x00026edc 0x003f8 0x0108c RW  0x8000
  DYNAMIC        0x016ee8 0x00026ee8 0x00026ee8 0x00118 0x00118 RW  0x4
  NOTE           0x000170 0x00008170 0x00008170 0x00044 0x00044 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
  GNU_RELRO      0x016edc 0x00026edc 0x00026edc 0x00124 0x00124 R   0x1

 Section to Segment mapping:
  Segment Sections...
   00     .ARM.exidx
   01
   02     .interp
   03     .interp .note.ABI-tag .note.gnu.build-id .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .ARM.exidx .eh_frame
   04     .init_array .fini_array .jcr .dynamic .got .data .bss
   05     .dynamic
   06     .note.ABI-tag .note.gnu.build-id
   07
   08     .init_array .fini_array .jcr .dynamic

其中 program header 對應到 "/usr/include/elf.h"
typedef struct
{
  Elf32_Word    p_type;                 /* Segment type */
  Elf32_Off     p_offset;               /* Segment file offset */
  Elf32_Addr    p_vaddr;                /* Segment virtual address */
  Elf32_Addr    p_paddr;                /* Segment physical address */
  Elf32_Word    p_filesz;               /* Segment size in file */
  Elf32_Word    p_memsz;                /* Segment size in memory */
  Elf32_Word    p_flags;                /* Segment flags */
  Elf32_Word    p_align;                /* Segment alignment */
} Elf32_Phdr;

一樣可以用 hexdump 來倒出對應的值, 其中參數 288 = 32 bytes * 9 header, offset 是 52 bytes
$ hexdump -n 288 -s 52 -C /bin/ls
00000034  01 00 00 70 c0 60 01 00  c0 e0 01 00 c0 e0 01 00  |...p.`..........|
00000044  28 00 00 00 28 00 00 00  04 00 00 00 04 00 00 00  |(...(...........|
00000054  06 00 00 00 34 00 00 00  34 80 00 00 34 80 00 00  |....4...4...4...|
00000064  20 01 00 00 20 01 00 00  05 00 00 00 04 00 00 00  | ... ...........|
00000074  03 00 00 00 54 01 00 00  54 81 00 00 54 81 00 00  |....T...T...T...|
00000084  19 00 00 00 19 00 00 00  04 00 00 00 01 00 00 00  |................|
00000094  01 00 00 00 00 00 00 00  00 80 00 00 00 80 00 00  |................|
000000a4  ec 60 01 00 ec 60 01 00  05 00 00 00 00 80 00 00  |.`...`..........|
000000b4  01 00 00 00 dc 6e 01 00  dc 6e 02 00 dc 6e 02 00  |.....n...n...n..|
000000c4  f8 03 00 00 8c 10 00 00  06 00 00 00 00 80 00 00  |................|
000000d4  02 00 00 00 e8 6e 01 00  e8 6e 02 00 e8 6e 02 00  |.....n...n...n..|
000000e4  18 01 00 00 18 01 00 00  06 00 00 00 04 00 00 00  |................|
000000f4  04 00 00 00 70 01 00 00  70 81 00 00 70 81 00 00  |....p...p...p...|
00000104  44 00 00 00 44 00 00 00  04 00 00 00 04 00 00 00  |D...D...........|
00000114  51 e5 74 64 00 00 00 00  00 00 00 00 00 00 00 00  |Q.td............|
00000124  00 00 00 00 00 00 00 00  06 00 00 00 04 00 00 00  |................|
00000134  52 e5 74 64 dc 6e 01 00  dc 6e 02 00 dc 6e 02 00  |R.td.n...n...n..|
00000144  24 01 00 00 24 01 00 00  04 00 00 00 01 00 00 00  |$...$...........|

以第一個 program header 來說, Elf32_Phdr 對應的值是:
p_type:   01 00 00 70 = 0x70000001
p_offset: c0 60 01 00 = 0x000160c0
p_vaddr:  c0 e0 01 00 = 0x0001e0c0
p_paddr:  c0 e0 01 00 = 0x0001e0c0
p_filesz: 28 00 00 00 = 0x00000028
p_memsz:  28 00 00 00 = 0x00000028
p_flags:  04 00 00 00 = 0x00000004
p_align:  04 00 00 00 = 0x00000004

其中 p_type 常見的值:

  • PT_LOAD (1) : 載入的 program section
  • PT_DYNAMIC (2) : 動態連結資訊
  • PT_INTERP (3) : program interpreter
  • PT_NOTE (4) : 輔助資訊
  • PT_PHDR (6) : program header本身
  • PT_TLS (7) : thread local storage

在 readelf 印出來的資料中, "Section to Segment mapping"底下的資料是每個program header對應的segment以及它包含的section list
比如說, 第1個 header 的 p_type 是 EXIDX, 它的 segment 是 index 00, 它包含的section是 ".ARM.exidx"
第4個 header的 p_type 是 LOAD, 它的 segment 是 index 03, 它包含的 section 是 ".interp .note.ABI-tag .note.gnu.build-id .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .ARM.exidx .eh_frame"

4. Section header


section header table 從 "Start of section headers" (e_shoff) 的 offset 開始
它的大小為 "Size of section headers" (e_shentsize) 乘以 "Number of section headers" (e_shnum)

"/bin/ls" 的 offset 是 95220 bytes, 大小為 40 bytes * 28 items = 1120 bytes

用 readelf 解讀:
$ readelf --section-headers /bin/ls
There are 28 section headers, starting at offset 0x173f4:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        00008154 000154 000019 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            00008170 000170 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            00008190 000190 000024 00   A  0   0  4
  [ 4] .hash             HASH            000081b4 0001b4 000380 04   A  6   0  4
  [ 5] .gnu.hash         GNU_HASH        00008534 000534 0003f8 04   A  6   0  4
  [ 6] .dynsym           DYNSYM          0000892c 00092c 0007d0 10   A  7   1  4
  [ 7] .dynstr           STRTAB          000090fc 0010fc 000592 00   A  0   0  1
  [ 8] .gnu.version      VERSYM          0000968e 00168e 0000fa 02   A  6   0  2
  [ 9] .gnu.version_r    VERNEED         00009788 001788 0000b0 00   A  7   5  4
  [10] .rel.dyn          REL             00009838 001838 000030 08   A  6   0  4
  [11] .rel.plt          REL             00009868 001868 000350 08   A  6  13  4
  [12] .init             PROGBITS        00009bb8 001bb8 00000c 00  AX  0   0  4
  [13] .plt              PROGBITS        00009bc4 001bc4 00050c 04  AX  0   0  4
  [14] .text             PROGBITS        0000a0d0 0020d0 010bd0 00  AX  0   0  8
  [15] .fini             PROGBITS        0001aca0 012ca0 000008 00  AX  0   0  4
  [16] .rodata           PROGBITS        0001aca8 012ca8 003418 00   A  0   0  4
  [17] .ARM.exidx        ARM_EXIDX       0001e0c0 0160c0 000028 00  AL 14   0  4
  [18] .eh_frame         PROGBITS        0001e0e8 0160e8 000004 00   A  0   0  4
  [19] .init_array       INIT_ARRAY      00026edc 016edc 000004 00  WA  0   0  4
  [20] .fini_array       FINI_ARRAY      00026ee0 016ee0 000004 00  WA  0   0  4
  [21] .jcr              PROGBITS        00026ee4 016ee4 000004 00  WA  0   0  4
  [22] .dynamic          DYNAMIC         00026ee8 016ee8 000118 08  WA  7   0  4
  [23] .got              PROGBITS        00027000 017000 0001bc 04  WA  0   0  4
  [24] .data             PROGBITS        000271c0 0171c0 000114 00  WA  0   0  8
  [25] .bss              NOBITS          000272d8 0172d4 000c90 00  WA  0   0  8
  [26] .ARM.attributes   ARM_ATTRIBUTES  00000000 0172d4 00002f 00      0   0  1
  [27] .shstrtab         STRTAB          00000000 017303 0000f1 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

而 section header 的定義為:
typedef struct
{
  Elf32_Word    sh_name;                /* Section name (string tbl index) */
  Elf32_Word    sh_type;                /* Section type */
  Elf32_Word    sh_flags;               /* Section flags */
  Elf32_Addr    sh_addr;                /* Section virtual addr at execution */
  Elf32_Off     sh_offset;              /* Section file offset */
  Elf32_Word    sh_size;                /* Section size in bytes */
  Elf32_Word    sh_link;                /* Link to another section */
  Elf32_Word    sh_info;                /* Additional section information */
  Elf32_Word    sh_addralign;           /* Section alignment */
  Elf32_Word    sh_entsize;             /* Entry size if section holds table */
} Elf32_Shdr;


常見的 section type:

  • SHT_PROGBITS (1) : 程式資料
  • SHT_SYMTAB (2) : 符號表
  • SHT_STRTAB (3) : 字串表
  • SHT_RELA (4) : relocation addition item
  • SHT_HASH (5) : symbol hash table
  • SHT_DYNAMIC (6) : 動態連結資訊
  • SHT_NOTE (7)
  • SHT_NOBITS (8) : 檔案裡不是資料的部份 (.bss)
  • SHT_REL (9) : relocatable item
  • SHT_DYNSYM (11) : dynamic linker 所使用的 symbol table
  • SHT_INIT_ARRAY (14) : constructor array (.init)
  • SHT_FINI_ARRAY (15) : destructor array (.fini)

在 elf header 裡, e_shstrndx 標明字串表的 section index, 這個例子裡 "Section header string table index" (e_shstrndx) 是 27, section header 的 name (sh_name) 是從這個 section 對應而來

在index 27, 它的 offset = 0x017303, size = 0xf1 (241), 用 hexdump把它印出來
$ hexdump -n 241 -s 0x017303 -C /bin/ls
00017303  00 2e 73 68 73 74 72 74  61 62 00 2e 69 6e 74 65  |..shstrtab..inte|
00017313  72 70 00 2e 6e 6f 74 65  2e 41 42 49 2d 74 61 67  |rp..note.ABI-tag|
00017323  00 2e 6e 6f 74 65 2e 67  6e 75 2e 62 75 69 6c 64  |..note.gnu.build|
00017333  2d 69 64 00 2e 67 6e 75  2e 68 61 73 68 00 2e 64  |-id..gnu.hash..d|
00017343  79 6e 73 79 6d 00 2e 64  79 6e 73 74 72 00 2e 67  |ynsym..dynstr..g|
00017353  6e 75 2e 76 65 72 73 69  6f 6e 00 2e 67 6e 75 2e  |nu.version..gnu.|
00017363  76 65 72 73 69 6f 6e 5f  72 00 2e 72 65 6c 2e 64  |version_r..rel.d|
00017373  79 6e 00 2e 72 65 6c 2e  70 6c 74 00 2e 69 6e 69  |yn..rel.plt..ini|
00017383  74 00 2e 74 65 78 74 00  2e 66 69 6e 69 00 2e 72  |t..text..fini..r|
00017393  6f 64 61 74 61 00 2e 41  52 4d 2e 65 78 69 64 78  |odata..ARM.exidx|
000173a3  00 2e 65 68 5f 66 72 61  6d 65 00 2e 69 6e 69 74  |..eh_frame..init|
000173b3  5f 61 72 72 61 79 00 2e  66 69 6e 69 5f 61 72 72  |_array..fini_arr|
000173c3  61 79 00 2e 6a 63 72 00  2e 64 79 6e 61 6d 69 63  |ay..jcr..dynamic|
000173d3  00 2e 67 6f 74 00 2e 64  61 74 61 00 2e 62 73 73  |..got..data..bss|
000173e3  00 2e 41 52 4d 2e 61 74  74 72 69 62 75 74 65 73  |..ARM.attributes|

也可以用 readelf印出來
$ readelf -x27 /bin/ls

Hex dump of section '.shstrtab':
  0x00000000 002e7368 73747274 6162002e 696e7465 ..shstrtab..inte
  0x00000010 7270002e 6e6f7465 2e414249 2d746167 rp..note.ABI-tag
  0x00000020 002e6e6f 74652e67 6e752e62 75696c64 ..note.gnu.build
  0x00000030 2d696400 2e676e75 2e686173 68002e64 -id..gnu.hash..d
  0x00000040 796e7379 6d002e64 796e7374 72002e67 ynsym..dynstr..g
  0x00000050 6e752e76 65727369 6f6e002e 676e752e nu.version..gnu.
  0x00000060 76657273 696f6e5f 72002e72 656c2e64 version_r..rel.d
  0x00000070 796e002e 72656c2e 706c7400 2e696e69 yn..rel.plt..ini
  0x00000080 74002e74 65787400 2e66696e 69002e72 t..text..fini..r
  0x00000090 6f646174 61002e41 524d2e65 78696478 odata..ARM.exidx
  0x000000a0 002e6568 5f667261 6d65002e 696e6974 ..eh_frame..init
  0x000000b0 5f617272 6179002e 66696e69 5f617272 _array..fini_arr
  0x000000c0 6179002e 6a637200 2e64796e 616d6963 ay..jcr..dynamic
  0x000000d0 002e676f 74002e64 61746100 2e627373 ..got..data..bss
  0x000000e0 002e4152 4d2e6174 74726962 75746573 ..ARM.attributes
  0x000000f0 00                                  .

section header 的 sh_name 是字串表的 offset, 比如說 sh_name=1的話, name就是".shstrtab", 後面有字串結尾'\0', 如果 sh_name=0x0b的話, name 就是 ".interp"

5. Symbol table


如果檔案有strip過, 那麼只會看到 dynamic symbol table
"/bin/ls" 的 section header index 06 它的 type 是 DYNSYM, 所以 dynamic symbol table存在這
也可以先用 readelf來解讀
$ readelf --syms /bin/ls

Symbol table '.dynsym' contains 125 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND __aeabi_unwind_cpp_pr0@GCC_3.5 (2)
     2: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
     4: 00009c08     0 FUNC    GLOBAL DEFAULT  UND getpwnam@GLIBC_2.4 (3)
......

如果要用hexdump的話, section header index 06 的 offset是0x92c, 大小是0x7d0
$ hexdump -n 32 -s 0x92c -C /bin/ls
0000092c  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
0000093c  cd 00 00 00 00 00 00 00  00 00 00 00 12 00 00 00  |................|

而symbol item 的 struct :
typedef struct
{
  Elf32_Word    st_name;                /* Symbol name (string tbl index) */
  Elf32_Addr    st_value;               /* Symbol value */
  Elf32_Word    st_size;                /* Symbol size */
  unsigned char st_info;                /* Symbol type and binding */
  unsigned char st_other;               /* Symbol visibility */
  Elf32_Section st_shndx;               /* Section index */
} Elf32_Sym;

struct大小為16 bytes, 以第2個item來說, 它對應的值是
st_name:  cd 00 00 00 = 0x000000cd
st_value: 00 00 00 00
st_size:  00 00 00 00
st_info:  12          = STB_GLOBAL | STT_FUNC
st_other: 00
st_shndx: 00 00

st_name 雖然也是參考 string table, 但它參考的是 dynamic string table, 也就是 section header index 07 的表

st_info 分成高位 4 bit 及低位 4 bit
高位4 bit為:

  • STB_LOCAL (0) : local symbol
  • STB_GLOBAL (1) : global symbol
  • STB_WEAK (2) : weak linking symbol
低位4 bit為:

  • STT_OBJECT (1) : data object
  • STT_FUNC (2) : function
  • STT_SECTION (3) : section
  • STT_FILE (4) : source file name which related to object
  • STT_COMMON (5) : share data
  • STT_TLS (6) : thread local data






2014/10/07

libllcp in nfc-tools

1. nfc-tools

nfc-tools 開發 nfc 相關的 tool, 最有名的應該是libnfc
底下是 nfc-tools 的網頁
https://code.google.com/p/nfc-tools/

但是libnfc還是太底層, 為了實作 NDEF 或 NPP, 可能還要裝 libfreefare 或 libllcp

libfreefare 是專門用在 Mifare
https://code.google.com/p/libfreefare/

libllcp 則是一般性的NDEF communication
https://code.google.com/p/libllcp/
關於怎麼install:
http://nfc-tools.org/index.php?title=Libllcp

LLCP (Logical Link Control Protocol), 有點像是 IP (Internet Protocol)
它可以經由建立連線之後, 由兩端傳data
而 LLCP 又實作了兩種 protocol
一個是 NPP (NDEF Push Protocol), 就像是 UDP 一樣, 丟封包出去就不用等
另個是 SNEP (Simple NDEF Exchange Protocol), 就像是 TCP, 丟封包出去要等ACK, (但沒有三方交握)

2. NPP sample code

在 libllcp/examples/npp-server/ 有個npp server的範例
它的用途是建立連線之後, 接收從tag來的資料

但是裡面似乎有些bug, 所以我將它重寫並測試過, sample code如下:
(錯的部份是, header 檔像 llcp.h 應該在 nfc/llcp.h底下, 然後stdout的部份會強制導到file)

#include <err.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#include <nfc/llcp.h>
#include <nfc/llc_service.h>
#include <nfc/llc_link.h>
#include <nfc/llc_connection.h>
#include <nfc/mac.h>

#define errx(code, ...) do { \
    fprintf (stderr, __VA_ARGS__); \
    fprintf (stderr, "\n"); \
    exit (code); \
  } while (0)

struct mac_link *mac_link;
nfc_device *device;

static void stop_mac_link(int sig) {
 if (mac_link && mac_link->device) {
  nfc_abort_command(mac_link->device);
 }
}

static void bye(VOID) {
 if (device) {
  nfc_close(device);
 }
}

static void print_hex(const uint8_t *pbtData, const size_t szBytes) {
 size_t szPos;
 for (szPos = 0; szPos < szBytes; szPos++) {
  printf("%02x ", pbtData[szPos]);
 }
 printf("\n");
}

static void* com_android_npp_thread(void *arg) {
 struct llc_connection *connection = (struct llc_connection *)arg;
 uint8_t buffer[1024];

 int len;
 size_t n = 0;

 len = llc_connection_recv(connection, buffer, sizeof(buffer), NULL);
 if (len < 0) return NULL;
 if (len < 10) return NULL; // NPP's header (5 bytes) + NDEF entry header (5 bytes)

 // byte 0 : protocol version
 printf("NDEF Push Protocol version: %02x\n", buffer[n]);
 if (buffer[n] != 0x01) return NULL; // version not supported
 n++;

 // byte 1~4 : number of NDEF entries
 uint32_t ndef_entries_count = be32toh(*((uint32_t *)(buffer+n)));
 printf("NDEF entries count: %u\n", ndef_entries_count);
 if (ndef_entries_count != 1) return NULL; // Version 0x01 has value 0x00 0x00 0x00 0x01
 n += 4;

 // NDEF Entry
 printf("Action code: %02x\n", buffer[n]);
 if (buffer[n] != 0x01) return NULL; // Action code not supported
 n++;

 uint32_t ndef_length = be32toh(*((uint32_t *)(buffer+n)));
 n += 4;

 if ((len-n) < ndef_length) return NULL; // less received bytes than expected?

 printf("NDEF entry received (%u bytes): ", ndef_length);
 print_hex(buffer + n, ndef_length);

 llc_connection_stop(connection);

 return NULL;
}

int main(int argc, char *argv[]) {

 int ret;

 if (llcp_init() < 0) errx(EXIT_FAILURE, "llcp_init()");

 signal(SIGINT, stop_mac_link);
 atexit(bye);

 nfc_context *context;
 nfc_init(&context);
 if (context == NULL) errx(EXIT_FAILURE, "nfc_init()");

 device = nfc_open(context, NULL);
 if (device == NULL) errx(EXIT_FAILURE, "Cannot connect to NFC device");

 struct llc_link *llc_link = llc_link_new();
 if (llc_link == NULL) errx(EXIT_FAILURE, "Cannot allocate LLC link data structures");

 struct llc_service *com_android_npp 
  = llc_service_new_with_uri(NULL, com_android_npp_thread, "com.android.npp", NULL);
 if (com_android_npp == NULL) errx(EXIT_FAILURE, "cannot create com.android.npp service");

 llc_service_set_miu(com_android_npp, 512);
 llc_service_set_rw(com_android_npp, 2);

 ret = llc_link_service_bind(llc_link, com_android_npp, -1);
 if (ret < 0) errx(EXIT_FAILURE, "Cannot bind service");

 mac_link = mac_link_new(device, llc_link);
 if (mac_link == NULL) errx(EXIT_FAILURE, "Cannot create MAC link");

 ret = mac_link_activate_as_target(mac_link);
 if (ret < 0) errx(EXIT_FAILURE, "Cannot create MAC link");

 void *err;
 mac_link_wait(mac_link, &err);

 mac_link_free(mac_link);
 llc_link_free(llc_link);

 nfc_close(device);
 device = NULL;

 llcp_fini();
 nfc_exit(context);
 exit(EXIT_SUCCESS);
}

然後將它compile, 其中 llcp 的部份官網寫 -lnfc-llcp是錯的
 $ gcc -o npp_server_example npp_server_example.c -lnfc -lllcp

有些手機仍舊可以接受NPP
我手上有台 HTC ONE, 將它的瀏覽器打開, 把手機放在 reader 上面
然後執行剛剛的程式, 手機會感應到, 手機螢幕顯示點選以傳送資料, 點它, 程式就會收到data, 最後將手機移開reader, 程式就結束

$ ./npp_server_example
NDEF Push Protocol version: 01
NDEF entries count: 1
Action code: 01
NDEF entry received (23 bytes): d1 01 13 55 04 74 77 2e 6d 6f 62 69 2e 79 61 68 6f 6f 2e 63 6f 6d 2f

其中
74 77 2e 6d 6f 62 69 2e 79 61 68 6f 6f 2e 63 6f 6d 2f = tw.mobi.yahoo.com/
(TODO: 前面header應該可以parse)

3. npp server code analysis


llcp_init() 是使用llcp要用的初始化, 裡面設定signal handler以及log function,
llcp_fini() 則是結束時釋放資源
int llcp_init(void)
int llcp_fini(void)

nfc context 以及 nfc device 的用法和 libnfc 相同

llc_link 用於紀錄 llc link 相關資訊
struct llc_link {
  uint8_t role;
  enum {
    LL_ACTIVATED,
    LL_DEACTIVATED,
  } status;
  struct llcp_version version;
  uint16_t local_miu;
  uint16_t remote_miu;
  uint16_t remote_wks;
  struct timeval local_lto;
  struct timeval remote_lto;
  uint8_t local_lsc;
  uint8_t remote_lsc;
  uint8_t opt;

  pthread_t thread;
  char *mq_up_name;
  char *mq_down_name;
  mqd_t llc_up;
  mqd_t llc_down;

  struct llc_service *available_services[MAX_LLC_LINK_SERVICE + 1];
  struct llc_connection *datagram_handlers[MAX_LOGICAL_DATA_LINK];
  struct llc_connection *transmission_handlers[MAX_LLC_LINK_SERVICE + 1];

  /* Unit tests metadata */
  void *cut_test_context;
  struct mac_link *mac_link;
};

用 llc_link_new() 來設定 llc link 的 default 值
struct llc_link* llc_link_new(void)


llc_service 用來建 LLC(logical link control) 會用到的相關設定
struct llc_service {
  char *uri;
  void *(*accept_routine)(void *);
  void *(*thread_routine)(void *);
  int8_t sap;
  uint8_t rw;
  uint16_t miu;
  void *user_data;
};

llc_service_new_with_uri() 則是設定 llc_service 成default
struct llc_service* llc_service_new_with_uri(void * (*accept_routine)(void *),
                                             void * (*thread_routine)(void *),
                                             const char *uri,
                                             void *user_data)
accept_routine 是 thread function, 在建立連線時(收到PDU_CONNECT) 呼叫
thread_routine 也是 thread function, 在收到 PDU_UI 時呼叫
uri 應該只是 llc service identify
user_data 可以送 data, 這個例子裡沒用到

有了相關設定之後, 可以bind service
int llc_link_service_bind(struct llc_link *link, struct llc_service *service, int8_t sap)


接著是 mac layer 相關設定
struct mac_link {
  enum { MAC_LINK_UNSET, MAC_LINK_INITIATOR, MAC_LINK_TARGET } mode;
  nfc_device *device;
  struct llc_link *llc_link;
  uint8_t nfcid[10];
  uint8_t buffer[BUFSIZ];
  size_t buffer_size;
  pthread_t *__restrict__ exchange_pdus_thread;
};

一樣有設定default值的function
struct mac_link* mac_link_new(nfc_device *device, struct llc_link *llc_link)

然後enable mac layer setting
int mac_link_activate_as_target(struct mac_link *mac_link)
裡面做的事是用 libnfc 選 target tag
其中 mac_link_run() 開了一條thread 用於傳送接收apdu
預設是
void* mac_link_exchange_pdus(void *arg)

最後用這個 function 等 mac_link_exchange_pdus 這條 thread 結束
int mac_link_wait(struct mac_link *link, void **value_ptr)

4. snep sample code

我一樣改寫了 libllcp 的 libllcp/examples/snep-server/

#include <err.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#include <nfc/llcp.h>
#include <nfc/llc_service.h>
#include <nfc/llc_link.h>
#include <nfc/llc_connection.h>
#include <nfc/mac.h>

#define LOG_DGB 1

#define errx(code, ...) do { \
    fprintf (stderr, __VA_ARGS__); \
    fprintf (stderr, "\n"); \
    exit (code); \
  } while (0)

struct mac_link *mac_link;
nfc_device *device;

static void stop_mac_link(int sig) {
 if (mac_link && mac_link->device) {
  nfc_abort_command(mac_link->device);
 }
}

static void bye(VOID) {
 if (device) {
  nfc_close(device);
 }
}

static void print_hex(const uint8_t *pbtData, const size_t szBytes) {
 size_t szPos;
 for (szPos = 0; szPos < szBytes; szPos++) {
  printf("%02x ", pbtData[szPos]);
 }
 printf("\n");
}

static void* com_android_snep_thread(void *arg) {
 struct llc_connection *connection = (struct llc_connection *) arg;
 uint8_t buffer[1024];
 uint8_t frame_snep_response_success[] = {
  0x10, 0x81, 0x00, 0x00, 0x00, 0x00
 };

 int len;
 len = llc_connection_recv(connection, buffer, sizeof(buffer), NULL);
 if (len < 0 || len < 7) {
  // SNEP's header (2 bytes) + NDEF entry header (5 bytes)
printf("ERR: invalid length\n"); return NULL; } size_t n = 0; printf("SNEP version: %d.%d\n", buffer[n]>>4, buffer[n] & 0x0f ); if (buffer[n] != 0x10) { printf("WRN: snep version other than version 1.0 may not support.\n"); } n++; uint8_t action_code = buffer[n]; n++; switch(action_code) { case 0x00: // Continue break; case 0x01: // GET break; case 0x02: // PUT { uint32_t ndef_length = be32toh(*((uint32_t *)(buffer + n))); if ((len - 6) < ndef_length) { printf("ERR: less received bytes than expected?\n"); return NULL; } n+=4; /** return snep success response package */ llc_connection_send(connection, frame_snep_response_success, sizeof(frame_snep_response_success)); print_hex(buffer + n, ndef_length); break; } default: printf("unknown action code\n"); break; } llc_connection_stop(connection); return NULL; } int main(int argc, char *argv[]) { int ret; if (llcp_init() < 0) errx(EXIT_FAILURE, "llcp_init()"); signal(SIGINT, stop_mac_link); atexit(bye); nfc_context *context; nfc_init(&context); if (context == NULL) errx(EXIT_FAILURE, "nfc_init()"); device = nfc_open(context, NULL); if (device == NULL) errx(EXIT_FAILURE, "Cannot connect to NFC device"); struct llc_link *llc_link = llc_link_new(); if (llc_link == NULL) errx(EXIT_FAILURE, "Cannot allocate LLC link data structures"); struct llc_service *com_android_snep = llc_service_new_with_uri(NULL, com_android_snep_thread, "urn:nfc:sn:snep", NULL); if (com_android_snep == NULL) errx(EXIT_FAILURE, "cannot create com.android.npp service"); llc_service_set_miu(com_android_snep, 512); llc_service_set_rw(com_android_snep, 2); ret = llc_link_service_bind(llc_link, com_android_snep, LLCP_SNEP_SAP); if (ret < 0) errx(EXIT_FAILURE, "Cannot bind service"); mac_link = mac_link_new(device, llc_link); if (mac_link == NULL) errx(EXIT_FAILURE, "Cannot create MAC link"); ret = mac_link_activate_as_target(mac_link); if (ret < 0) errx(EXIT_FAILURE, "Cannot create MAC link"); void *err; mac_link_wait(mac_link, &err); mac_link_free(mac_link); llc_link_free(llc_link); nfc_close(device); device = NULL; llcp_fini(); nfc_exit(context); exit(EXIT_SUCCESS); }


compile 之後, 實驗方式跟 npp server 一樣

5. code analysis


整份 code 跟 npp server 很像, 但有些小差異
在 llc_link_service_bind(), 後面帶的 sap 是 LLCP_SNEP_SAP

另個差異是 header 不一樣

然後在收到data之後, 要回傳OK packet

其它的就沒有差異了




2014/10/06

NFC ISO14443A

1. ISO14443

ISO14443 定義描寫了近距離接觸(<10cm)的卡片, 運作在13.56 MHz, 以及card & reader之間的溝通方式, 分成 ISO14443A 及 ISO14443B, 其中 ISO14443A 最為普及

2. ISO14443A

在 NFC forum 定義的 4 種NFC tag type裡, ISO14443A佔了 type1, type2, type4, 還有Mifare classic tag雖然不在NFC forum定義裡, 但也實作了 ISO14443A

在辨識 tag 的過程中, anti-collision 可以得到辨識 tag 所需的資訊, 包括 ATQA, UID, SAK, ATS

  • ATQA : Answer To Request acc. to ISO/IEC 14443-4
  • UID : Unique Identifier, Type A
  • SAK : Select Acknowledge, Type A
  • ATS : Answer To Select acc. to ISO/IEC 14443-4
相關文件可以參考identify Mifare tags
http://www.nxp.com/documents/application_note/AN10833.pdf

3. anti-collision

anti-collision 可以實作在 firmware, 也可以實作在 userspace, 但是中間涉及到傳送7 bit data, 大多數的 driver 不允許傳送非8-bit單位的data, 所以如果沒有對應的SDK就無法實作在userspace

下圖是anti-collision流程圖

(1) 第一步由 reader 往外送 REQA(0x26) 或 WUPA(0x52), 這兩個訊號都是 7-bit, 這是為了讓這兩個 command 可以與其它 command 做區隔, 也因為這兩個 command 都是 7-bit, 所以要將 CRC 的位元檢查關掉

REQA 作用是邀請在整個區域裡新的 tag 來回應這個 command, 整個區域裡也有舊的 tag, 指的是曾經回應過或已經被關掉的tag

WUPA 則是邀請並喚醒區域裡所有tag, 包括回應過或關掉的tag

之所以有新舊的差別, 是因為只要是卡片未離開 reader 的區域, 就會被持續供電

如果沒有任何 tag 回應, 那麼 reader 會持續發出 REQA/WUPA, 中間的間隔雖然有被定義, 但各家廠商作法不同, 有的等上1s, 有的則是等幾個milliseconds,

(2) 接著 tag 回應 ATQA, 此時 reader 開始 anti-collision 的流程, 區分每張卡片的方式是使用 tag的 UID, UID的長度可能是4 bytes, 7 bytes, 或 10 bytes

(3) reader 接著發出 SELECT

(4) tag 回應 UID

(5) reader 發出 SELECT 並帶 UID, 成功的話, tag 會回應 SAK

底下是 libnfc 的 sample flow
http://nfc-tools.org/index.php?title=Libnfc:nfc-anticol

R: 26                                    => Welcome (REQA) (or use WUPA = 0x52)
T: 44  03                                => Respond (ATQA)
R: 93  20                                => Select cascade 1 (SEL)
T: 88  04  34  74  cc                    => CT, UID(byte 1,2,3), BCC
R: 93  70  88  04  34  74  cc  0e  05    => Select available tag (SEL)
T: 24  d8  36                            => Select Acknowledge (SAK) with cascade bit set
R: 95  20                                => Select cascade 2 (SEL)    
T: e1  e3  1c  80  9e                    => UID(byte 4,5,6,7), BCC
R: 95  70  e1  e3  1c  80  9e  b9  e1    => Finish select (SEL)
T: 20  fc  70                            => SAK without cascade bit set
R: e0  50  bc  a5                        => Request Answer to Select (RATS)
T: 06  75  77  81  02  80                => ATS (DESFire EV1)
R: 50  00  57  cd                        => Disable (HALT)

Found tag with UID: 043474e1e31c80

CT  => [Cascade Tag] byte (88), signals that the UID is not complete yet
BCC => [Bit Count Check] byte, calculated as exclusive-or over the four previous bytes

整個流成跑完之後, 其中 ATQA, SAK, ATS 包含廠商資訊
http://nfc-tools.org/index.php?title=ISO14443A







libnfc example

1. Brief Introduction


在使用libnfc相關的code, 第一步一定要先呼叫 nfc_init(), 它會做初始設定和配置相關資源
void nfc_init(nfc_context **context)


接著呼叫 nfc_open(), 它會找第一個可用的 nfc device
nfc_device* nfc_open(nfc_context *context, const nfc_connstring connstring)


參數 context 就是在 nfc_init() 用到的那個
參數 connstring 表示你想找有特定名字的device, 像"usb"之類的, 如果沒有特定就帶NULL

它會依照下列順序找:
(1) 環境變數裡預設值
(2) libnfc.conf 設定的值
(3) /etc/nfc/devices.d 裡找到
(4) auto-detected 第一個找到的值

找到之後, 它會宣告目前它在使用這個device, 並回傳device pointer


然後記得對這個 device 做初始化
int nfc_initiator_init(nfc_device *pnd)


struct nfc_modulation 表示不同ISO規格的資料
typedef struct {
  nfc_modulation_type nmt;
  nfc_baud_rate nbr;
} nfc_modulation;
其中 nfc_modulation_type為ISO標準
typedef enum {
  NMT_ISO14443A = 1,
  NMT_JEWEL,
  NMT_ISO14443B,
  NMT_ISO14443BI, // pre-ISO14443B aka ISO/IEC 14443 B' or Type B'
  NMT_ISO14443B2SR, // ISO14443-2B ST SRx
  NMT_ISO14443B2CT, // ISO14443-2B ASK CTx
  NMT_FELICA,
  NMT_DEP,
} nfc_modulation_type;
然後 nfc_baud_rate 為 tag 的 baud rate, (每種規格有不同的baud rate)
typedef enum {
  NBR_UNDEFINED = 0,
  NBR_106,
  NBR_212,
  NBR_424,
  NBR_847,
} nfc_baud_rate;
nfc_modulation 主要用在描述現在想嘗試找什麼規格的 tag


struct nfc_target 則是用來儲存找到的 tag
typedef struct {
  nfc_target_info nti;
  nfc_modulation nm;
} nfc_target;
其中 nfc_modulation 只是紀錄它的ISO規格
union nfc_target_info 則是儲存找到的這張 tag 的內容
typedef union {
  nfc_iso14443a_info nai;
  nfc_felica_info nfi;
  nfc_iso14443b_info nbi;
  nfc_iso14443bi_info nii;
  nfc_iso14443b2sr_info nsi;
  nfc_iso14443b2ct_info nci;
  nfc_jewel_info nji;
  nfc_dep_info ndi;
} nfc_target_info;
不同的 ISO 規格, 它能提供的資訊也不同


我們有nfc device, nfc modulation, 就可以找 nfc target, 用底下這個function
int nfc_initiator_select_passive_target(nfc_device *pnd,
                                        const nfc_modulation nm,
                                        const uint8_t *pbtInitData,
                                        const size_t szInitData,
                                        nfc_target *pnt)
其中 pnd 是 nfc device
nm 是  nfc modulation
pbtInitData 可以帶 NULL, 有些 ISO 規格裡可以帶data給tag, 比如說ISO14443A裡, 可以帶 UID, 而 ISO14443B 可以帶 Application Family Identifier (AFI)
szInitData 則是前一項 pbtInitData的size
pnt 則是存找到的nfc target


最後記得要關device
void nfc_close(nfc_device *pnd)

以及要記得釋放nfc context資源
void nfc_exit(nfc_context *context)

2. Sample code


這份sample code從libnfc官方網頁上可以找到, 這是原始網頁
http://nfc-tools.org/index.php?title=Libnfc:quick_start_example

它可以找 ISO14443-A tag, (可能是最常見的tag)

#include <stdlib.h>
#include <nfc/nfc.h>

static void
print_hex(const uint8_t *pbtData, const size_t szBytes) {
 size_t szPos;

 for (szPos = 0; szPos < szBytes; szPos++) {
  printf("%02x ", pbtData[szPos]);
 }
 printf("\n");
}

int
main(int argc, const char *argv[]) {
 nfc_device *pnd;
 nfc_target nt;
 nfc_context *context;

 nfc_init(&context);
 if (context == NULL) {
  printf("Unable to init libnfc (malloc)\n");
  exit(EXIT_FAILURE);
 }

 pnd = nfc_open(context, NULL);
 if (pnd == NULL) {
  printf("ERROR: %s\n", "Unable to open NFC device.");
  exit(EXIT_FAILURE);
 }

 if (nfc_initiator_init(pnd) < 0) {
  nfc_perror(pnd, "nfc_initiator_init");
  exit(EXIT_FAILURE);
 }

 printf("NFC reader: %s opened\n", nfc_device_get_name(pnd));

 const nfc_modulation nmMifare = {
  .nmt = NMT_ISO14443A,
  .nbr = NBR_106,
 };
 if (nfc_initiator_select_passive_target(pnd, nmMifare, NULL, 0, &nt) > 0) {
  printf("The following (NFC) ISO14443A tag was found:\n");
  printf("    ATQA (SENS_RES): ");
  print_hex(nt.nti.nai.abtAtqa, 2);
  printf("       UID (NFCID%c): ", (nt.nti.nai.abtUid[0] == 0x08 ? '3' : '1'));
  print_hex(nt.nti.nai.abtUid, nt.nti.nai.szUidLen);
  printf("      SAK (SEL_RES): ");
  print_hex(&nt.nti.nai.btSak, 1);
  if (nt.nti.nai.szAtsLen) {
   printf("          ATS (ATR): ");
   print_hex(nt.nti.nai.abtAts, nt.nti.nai.szAtsLen);
  }
 }

 nfc_close(pnd);

 nfc_exit(context);
 exit(EXIT_SUCCESS);
}

然後 compile它
$ gcc -o quick_start_example1 quick_start_example1.c -lnfc

執行結果
$ ./quick_start_example1
NFC reader: pn532_spi:/dev/spidev0.0 opened
The following (NFC) ISO14443A tag was found:
    ATQA (SENS_RES): 00 04
       UID (NFCID1): 45 d1 a9 51
      SAK (SEL_RES): 08

3. Polling


前個例子是一執行馬上就掃當下讀的到的卡
也可以改成用 poll 的方式, 在一定的時間內讀到卡即可

int nfc_initiator_poll_target(nfc_device *pnd,
                              const nfc_modulation *pnmModulations,
                              const size_t szModulations,
                              const uint8_t uiPollNr,
                              const uint8_t uiPeriod,
                              nfc_target *pnt)
其中, pnd 是 nfc device, pnt 是 nfc target
pnmModulations 是 nfc_modulation array, 裡面放想掃的 modulation type
szModulations 是 pnmModulations 的 array 長度
uiPollNr 指要 poll 幾次, 可以 1~254 次, 如果是 0xFF 表示要永遠 poll
uiPeriod 表是每次 poll 間隔幾個單位, 1個單位內定是150ms, 2個單位就是300ms, 以此類推
回傳值是 poll 掃到的 nfc target 數目, <0 表示error, =0表示沒掃到

可以 poll 卡是不是接近之後, 也可以檢查卡是不是已經拿開
int nfc_initiator_target_is_present(nfc_device *pnd, const nfc_target *pnt)
回傳 0 表示還在, 其它的值表示 error (即離開或讀不到了)

3.1 Polling sample code


相關的 sample code 可以參考 libnfc 裡的 nfc-poll.c
底下這個例子稍微簡化 nfc-poll.c,

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

#include <nfc/nfc.h>
#include <nfc/nfc-types.h>

static nfc_device *pnd = NULL;
static nfc_context *context;

static void print_hex(const uint8_t *pbtData, const size_t szBytes) {
 size_t szPos;
 for (szPos = 0; szPos < szBytes; szPos++) {
  printf("%02x ", pbtData[szPos]);
 }
 printf("\n");
}

static void stop_polling(int sig) {
 if (pnd != NULL) {
  nfc_abort_command(pnd);
 } else {
  nfc_exit(context);
  exit(EXIT_FAILURE);
 }
}

int main(int argc, const char *argv[]) {
 signal(SIGINT, stop_polling);

 const uint8_t uiPollNr = 20;
 const uint8_t uiPeriod = 2;
 const nfc_modulation nmModulations[1] = {
  { .nmt = NMT_ISO14443A, .nbr = NBR_106 },
 };
 const size_t szModulations = 1;

 nfc_target nt;
 int res = 0;

 nfc_init(&context);
 if (context == NULL) {
  printf("Unable to init libnfc (malloc)\n");
  exit(EXIT_FAILURE);
 }

 pnd = nfc_open(context, NULL);
 if (pnd == NULL) {
  printf("Unable to open NFC device\n");
  exit(EXIT_FAILURE);
 }

 if (nfc_initiator_init(pnd) < 0) {
  nfc_perror(pnd, "nfc_initiator_init");
  nfc_close(pnd);
  nfc_exit(context);
  exit(EXIT_FAILURE);
 }

 printf("NFC reader: %s opened\n", nfc_device_get_name(pnd));
 printf("NFC device will poll %d ms\n", uiPollNr * szModulations * uiPeriod * 150);
 res = nfc_initiator_poll_target(pnd, nmModulations, szModulations, uiPollNr, uiPeriod, &nt);
 if (res < 0) {
  nfc_perror(pnd, "nfc_initiator_poll_target");
  nfc_close(pnd);
  nfc_exit(context);
  exit(EXIT_FAILURE);
 } else if (res == 0) {
  printf("No target found\n");
 } else {
  printf("The following (NFC) ISO14443A tag was found:\n");
  printf("    ATQA (SENS_RES): ");
  print_hex(nt.nti.nai.abtAtqa, 2);
  printf("       UID (NFCID%c): ", (nt.nti.nai.abtUid[0] == 0x08 ? '3' : '1'));
  print_hex(nt.nti.nai.abtUid, nt.nti.nai.szUidLen);
  printf("      SAK (SEL_RES): ");
  print_hex(&nt.nti.nai.btSak, 1);
  if (nt.nti.nai.szAtsLen) {
   printf("          ATS (ATR): ");
   print_hex(nt.nti.nai.abtAts, nt.nti.nai.szAtsLen);
  }
 }

 printf("Waiting for card removing...\n");
 while (0 == nfc_initiator_target_is_present(pnd, NULL)) {};
 nfc_perror(pnd, "nfc_initiator_target_is_present");
 printf("done\n");

 nfc_close(pnd);
 nfc_exit(context);
 exit(EXIT_SUCCESS);
}






2014/10/03

Connect PN532 NFC to Raspberry Pi

1. Enable SPI on Raspberry Pi


PN532 NFC提供3種介面, SPI, I2C, 與UART
先選SPI做測試,

在RPi上面, SPI與I2C預設是關的, 我們要先打開它,

$ sudo nano /etc/modprobe.d/raspi-blacklist.conf


會看到裡面有一些黑名單

blacklist spi-bcm2708
blacklist i2c-bcm2708
blacklist snd-soc-pcm512x
blacklist snd-soc-wm8804


我們要用到spi, 所以把它mark掉

#blacklist spi-bcm2708

存檔離開, 然後重開機讓設定生效


重開機之後, 來看看SPI driver有沒有起來

$ ls /dev/spidev*
/dev/spidev0.0  /dev/spidev0.1

會看到有兩個 SPI driver, 前面的數字是第幾個SPI peripheral,
兩個數字都是1表示 RPi 只有一個SPI port
第二個數字是 Chip Selectt 使用pin CS0 & CS1


2. Introduction to SPI


Serial Peripheral Interface (SPI), 跟 I2C很像
是同步序列資料協定(synchronous serial data link)

常用的接角定義:
  • SCLK—Serial Clock(自master輸出), 有時也叫SCK
  • MOSI/SIMO—Master Output, Slave Input(自master輸出)
  • MISO/SOMI—Master Input, Slave Output(自slave輸出)
  • SS—Slave Select(active low;自master輸出), 在RPi上叫SPI_CE0_N, SPI_CE1_N, 在PN532上叫SSEL

在使用上, 1個master可以對應多個slave
可能的接法如下
如果device扮演的是Master, 那麼它的MOSI就等於是它的Output, MISO就是它的Input
如果device扮演的是Slave, 那麼它的MOSI就等於是它的Input, MISO就是它的Output

3. Connect PN532 to RPi via SPi


底下是 RPi 的接腳配置


其中跟 SPI 相關的有:
SPI_MOSI
SPI_MISO
SPI_SCLK
SPI_CE0_N
SPI_CE1_N

我們這樣接:

  • PN532 的 3.3V 接到 RPi 的3V3 (可接1或17的位置)
  • PN532 的 GND 接到 RPi 的接地 (可接 6, 9, 14, 20, 25, 30, 34, 39, 我是接39)
  • PN532 的 SCK 接到 RPi 的 SPI_SCLK (23的位置)
  • PN532 的 MISO 接到 RPi 的 SPI_MISO (21的位置)
  • PN532 的 MOSI 接到 RPi 的 SPI_MOSI (19的位置)
  • PN532 的 SSEL 接到 RPi 的SPI_CE0_N


4. Install libnfc in RPi


接好之後, 有很多種方式可以與PN532溝通, 用libnfc是其中一種方便的方式

首先先更安裝有dependency的library
sudo apt-get update
$ sudo apt-get install libusb-dev libpcsclite-dev

然後抓libnfc source code, 可以上它們的網站來找
https://code.google.com/p/libnfc/

其中stable version在寫這篇文章的時候在這裡
https://bintray.com/nfc-tools/sources/libnfc

抓好之後, 將它解壓縮

$ tar -xf libnfc-1.7.1.tar.bz2

然後compile & install
$ cd libnfc-1.7.1
$ ./configure --prefix=/usr --sysconfdir=/etc
$ make
$ sudo make install

修改它的config
$ cd /etc
$ sudo mkdir nfc
$ sudo nano /etc/nfc/libnfc.conf

把底下copy paste
# Allow device auto-detection (default: true)
# Note: if this auto-detection is disabled, user has to set manually a device
# configuration using file or environment variable
allow_autoscan = true

# Allow intrusive auto-detection (default: false)
# Warning: intrusive auto-detection can seriously disturb other devices
# This option is not recommended, user should prefer to add manually his device.
allow_intrusive_scan = false

# Set log level (default: error)
# Valid log levels are (in order of verbosity): 0 (none), 1 (error), 2 (info), 3 (debug)
# Note: if you compiled with --enable-debug option, the default log level is "debug"
log_level = 1

# Manually set default device (no default)
# To set a default device, you must set both name and connstring for your device
# Note: if autoscan is enabled, default device will be the first device available in device list.
device.name = "Itead_PN532_SPI"
device.connstring = "pn532_spi:/dev/spidev0.0:500000"

設定完成, 來試試看有沒有問題, 拿一張悠遊卡放在PN532旁邊
$ nfc-list
$ nfc-list
nfc-list uses libnfc 1.7.1
NFC device: pn532_spi:/dev/spidev0.0 opened
1 ISO14443A passive target(s) found:
ISO/IEC 14443A (106 kbps) target:
    ATQA (SENS_RES): 00  04
       UID (NFCID1): 2b  6a  20  87
      SAK (SEL_RES): 08

會動之後, 就可以玩其它功能了





2014/10/01

Setting raspberry pi uart

1. How to connect


首先需要一條 USB-TTL 線
(試過RS232轉TTL, 似乎不成功)

底下是 Raspberry Pi (RPi) B+的 40-pin 配置圖


其中
USB-TTL 接地線(GND) 接到 RPi GND (JB6)
USB-TTL RX 接到 RPi TX (JB8)
USB-TTL TX 接到 RPi RX (JB10)

2. Serail setting


用putty或其它serial terminal (Ex. tera term)

類型選 serial
Serail Line 填 USB-TTL 的 COM port
Speed 填 115200
Data Bits 填 8
Stop Bits 填 1
Parity 填 None
Flow Control 填 None


3. screen size


登入之後會發現, 它的 row = 24, column = 80, 使用起來不方便
用 vim 輸入幾個tab就超出螢幕了
可以用 stty 來修改

這個可以看當前的設定
$ stty size

修改行數為40

$ stty rows 40

修改每行字元數為128

$ stty columns 128