2014/11/25

objdump

1. Use objdump dump ELF binary


如果想 dump 每個 section 的 binary, 可以用objdump

$ objdump --full-contents /bin/ls
Contents of section .interp:
 400238 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-
 400248 7838362d 36342e73 6f2e3200           x86-64.so.2.
Contents of section .note.ABI-tag:
 400254 04000000 10000000 01000000 474e5500  ............GNU.
 400264 00000000 02000000 06000000 18000000  ................
Contents of section .note.gnu.build-id:
 400274 04000000 14000000 03000000 474e5500  ............GNU.
 400284 64d095bc 6589dd4b fbf1c6d6 2ae98538  d...e..K....*..8
 400294 5965461b                             YeF.
Contents of section .gnu.hash:
 400298 03000000 72000000 02000000 07000000  ....r...........
 4002a8 a201400c 12010c3f 28440003 a8040000  ..@....?(D......
 4002b8 72000000 75000000 7e000000 281d8c1c  r...u...~...(...
 4002c8 4245d5ec bbe3927c bc50769e 86f0967c  BE.....|.Pv....|
 4002d8 96a08997 3cad390d d871581c ce2c6372  ....<.9..qX..,cr
 4002e8 e46241f5 b88df10e 39f28b1c 32c4f712  .bA.....9...2...
 4002f8 ead3ef0e b3a2f712                    ........

但示通常會印出一堆, 所以通常會看特定的section
可以用 objdump 找 section
$ objdump --section-headers /bin/ls

/bin/ls:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .interp       0000001c  0000000000400238  0000000000400238  00000238  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.ABI-tag 00000020  0000000000400254  0000000000400254  00000254  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .note.gnu.build-id 00000024  0000000000400274  0000000000400274  00000274  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .gnu.hash     00000068  0000000000400298  0000000000400298  00000298  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .dynsym       00000c18  0000000000400300  0000000000400300  00000300  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .dynstr       00000593  0000000000400f18  0000000000400f18  00000f18  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .gnu.version  00000102  00000000004014ac  00000000004014ac  000014ac  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .gnu.version_r 00000090  00000000004015b0  00000000004015b0  000015b0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .rela.dyn     000000a8  0000000000401640  0000000000401640  00001640  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .rela.plt     00000a80  00000000004016e8  00000000004016e8  000016e8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 10 .init         0000001a  0000000000402168  0000000000402168  00002168  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 11 .plt          00000710  0000000000402190  0000000000402190  00002190  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .text         0000f65a  00000000004028a0  00000000004028a0  000028a0  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
......

找到想要印的section
$ objdump --full-contents --section .interp /bin/ls

/bin/ls:     file format elf64-x86-64

Contents of section .interp:
 400238 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-
 400248 7838362d 36342e73 6f2e3200           x86-64.so.2.

也可以指定範圍, 比如說 .interp 從 0x400238 到 0x400253
$ objdump --full-contents --start-address=0x400238 --stop-address=0x400253 /bin/ls

/bin/ls:     file format elf64-x86-64

Contents of section .interp:
 400238 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-
 400248 7838362d 36342e73 6f2e32             x86-64.so.2

如果指定的範圍超過 section 邊界的話, 它會將跨過的 section 名字也印出來
$ objdump --full-contents --start-address=0x400238 --stop-address=0x400280 /bin/ls

/bin/ls:     file format elf64-x86-64

Contents of section .interp:
 400238 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-
 400248 7838362d 36342e73 6f2e3200           x86-64.so.2.
Contents of section .note.ABI-tag:
 400254 04000000 10000000 01000000 474e5500  ............GNU.
 400264 00000000 02000000 06000000 18000000  ................
Contents of section .note.gnu.build-id:
 400274 04000000 14000000 03000000           ............

如果只想把 ELF 檔案當 binary 來印, 或是想印某些純 binary 檔
$ objdump --full-contents -b binary /bin/ls


2. Use objdump to disassemble file


假如有個 "hello.c", 內容如下:
// hello.c
#include <stdio.h>

int main() {
        printf("Hello World\n");
        return 0;
}

然後 compile : "gcc -c -g hello.c", "gcc -o hello hello.o"

可以用 objdump 反組譯
$ objdump -d hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   bf 00 00 00 00          mov    $0x0,%edi
   9:   e8 00 00 00 00          callq  e <main+0xe>
   e:   b8 00 00 00 00          mov    $0x0,%eax
  13:   5d                      pop    %rbp
  14:   c3                      retq

使用 -d (--disassemble) 反組譯, 通常只會包含執行碼的 section (.text之類的)
如果想反組譯所有 section, 可以使用 -D ( --disassemble-all ), 但是會把不是程式碼的部份也當成程式碼反組譯

反組譯的輸出格式是
address <symbol>
    address:    code byte sequence        disassemble result

如果不想看到 code byte sequence
$ objdump -d --no-show-raw-insn hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:   push   %rbp
   1:   mov    %rsp,%rbp
   4:   mov    $0x0,%edi
   9:   callq  e <main+0xe>
   e:   mov    $0x0,%eax
  13:   pop    %rbp
  14:   retq


如果想看到逐行加上 symbol 的位置
$ objdump -d --prefix-address hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:
0000000000000000 <main> push   %rbp
0000000000000001 <main+0x1> mov    %rsp,%rbp
0000000000000004 <main+0x4> mov    $0x0,%edi
0000000000000009 <main+0x9> callq  000000000000000e <main+0xe>
000000000000000e <main+0xe> mov    $0x0,%eax
0000000000000013 <main+0x13> pop    %rbp
0000000000000014 <main+0x14> retq

此時 code byte sequence 就會被忽略, 如果想加回code byte sequence
$ objdump -d --prefix-address --show-raw-insn hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:
0000000000000000 <main> 55                      push   %rbp
0000000000000001 <main+0x1> 48 89 e5                    mov    %rsp,%rbp
0000000000000004 <main+0x4> bf 00 00 00 00              mov    $0x0,%edi
0000000000000009 <main+0x9> e8 00 00 00 00              callq  000000000000000e <main+0xe>
000000000000000e <main+0xe> b8 00 00 00 00              mov    $0x0,%eax
0000000000000013 <main+0x13> 5d                         pop    %rbp
0000000000000014 <main+0x14> c3                         retq


如果只想反組譯特定的 section
$ objdump -d --section .init hello

hello:     file format elf64-x86-64


Disassembly of section .init:

00000000004003e0 <_init>:
  4003e0:       48 83 ec 08             sub    $0x8,%rsp
  4003e4:       48 8b 05 0d 0c 20 00    mov    0x200c0d(%rip),%rax        # 600ff8 <_DYNAMIC+0x1d0>
  4003eb:       48 85 c0                test   %rax,%rax
  4003ee:       74 05                   je     4003f5 <_init+0x15>
  4003f0:       e8 3b 00 00 00          callq  400430 <__gmon_start__@plt>
  4003f5:       48 83 c4 08             add    $0x8,%rsp
  4003f9:       c3                      retq

反組譯的部份跟之前一樣, 也可以設定 --start-address 以及 --stop-address

如果 object file 包含 debug information, 加上 -l (--line-numbers) 可以把指令與原始碼行號對應資訊印出來, 但如果 object file 沒有 debug information 就沒有任何效果
$ objdump -d -l hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
main():
/home/william/temp/hello.c:3
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
/home/william/temp/hello.c:4
   4:   bf 00 00 00 00          mov    $0x0,%edi
   9:   e8 00 00 00 00          callq  e <main+0xe>
/home/william/temp/hello.c:5
   e:   b8 00 00 00 00          mov    $0x0,%eax
/home/william/temp/hello.c:6
  13:   5d                      pop    %rbp
  14:   c3                      retq


如果加上 -S (--source), 如果找的到原始檔就會顯示對應的原始碼, 但如果 object file 沒有 debug information 就沒有作用

$ objdump -d -S hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
#include <stdio.h>

int main() {
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
        printf("Hello World\n");
   4:   bf 00 00 00 00          mov    $0x0,%edi
   9:   e8 00 00 00 00          callq  e <main+0xe>
        return 0;
   e:   b8 00 00 00 00          mov    $0x0,%eax
}
  13:   5d                      pop    %rbp
  14:   c3                      retq

-S 與 -l 可以同時使用
其中可以注意的是, 在 printf 下面的 address 4, bf 00 00 00 00, 後面的位置都是00, 是因為檔案還沒 reallocate, 在 linking 之後才會嵌入位置
$ objdump -S hello

......
000000000040052d <main>:
  40052d:       55                      push   %rbp
  40052e:       48 89 e5                mov    %rsp,%rbp
  400531:       bf d4 05 40 00          mov    $0x4005d4,%edi
  400536:       e8 d5 fe ff ff          callq  400410 <puts@plt>
  40053b:       b8 00 00 00 00          mov    $0x0,%eax
  400540:       5d                      pop    %rbp
  400541:       c3                      retq
  400542:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  400549:       00 00 00
  40054c:       0f 1f 40 00             nopl   0x0(%rax)
......

可以看到執行檔裡已經嵌入位置 bf d4 05 40 00



















FreeRTOS Interrupt Management

1. Introduction


關於處理event, 有幾個要考慮的點

  • 要怎麼偵測event? 通常會用 interrupt, 但也可以用polling的方式
  • 如果使用 interrupt, 要在 ISR (Interrupt service routine) 裡面處裡多少事情? 相對地要在ISR外面處理多少事情? 是否 ISR 裡面的事情要愈少愈好?
  • ISR 裡的 code 要如何跟外面的 code 溝通? 尤其是 asynchronous 的code
FreeRTOS 沒有規定 event handle要怎麼實作, 但提供了一些interrupt API
在這些API裡, 只有 FromISR 結尾的 function 才可以在 ISR 裡呼叫, 以及以 FROM_ISR 結尾的 macro 可以在ISR裡使用

2. Binary Semaphores used for Synchronization


整個概念如下圖, 當 interrupt 發生時, ISR 解開 semaphore 並讓 handler task 變成 ready state, 然後 ISR return, 這時 handler task 有最高的 priority 並執行, 當它執行完, 等下一個 semaphore, 進入block state, 並讓低 priority 的 task 執行



在使用 semaphore 時, 分成 take & give, 實作上, 它就像是長度為1的queue, 它一樣要設定 block time, 相關的限制也相同

2.1 Create Semaphore


void vSemaphoreCreateBinary( xSemaphoreHandle xSemaphore );

xSemaphoreHandle 是 semaphore 的型態, 在使用它之前必需先 create

2.2 Take Semaphore


portBASE_TYPE xSemaphoreTake( xSemaphoreHandle xSemaphore, portTickType xTicksToWait );

如果把 xTicksToWait 設成 portMAX_DELAY, 表示要永遠等下去

xSemaphoreTake 不能在 ISR 裡面使用

可能的回傳值 :
  • pdPASS : 成功拿到 semaphore, 如果 block time > 0, 表示在 timeout 前拿到 semaphore
  • pdFALSE : 沒能在 block time timeout 前拿到 semaphore

2.3 Give Semaphore in ISR


portBASE_TYPE xSemaphoreGiveFromISR( xSemaphoreHandle xSemaphore,
                                     portBASE_TYPE *pxHigherPriorityTaskWoken
                                     );

如果有 high priority task 在等這個 semaphore, 然後呼叫 xSemaphoreGiveFromISR 造成目前的 task 被 scheduler switch out, 那麼會把 pxHigherPriorityTaskWoken 這個 pointer 裡面的值設成 pdTRUE, 表示要處裡目前要被 switch out 的 task 的 context switch

可能的回傳值 :

  • pdPASS : 成功
  • pdFALSE : 如果目前的 semaphore 已經被 give 了, 會回傳失敗

2.4 sample code


static void vPeriodicTask( void *pvParameters )
{
    for( ;; )
    {
        /* 等 500ms 準備送出 sofeware interrupt */
        vTaskDelay( 500 / portTICK_RATE_MS );

        /* 送出 interrupt, 前後夾 log */
        vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
        __asm{ int 0x82 } /* This line generates the interrupt. */
        vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
    }
}

static void vHandlerTask( void *pvParameters )
{
    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ;; )
    {
        /* take semaphore */
        xSemaphoreTake( xBinarySemaphore, portMAX_DELAY );
        vPrintString( "Handler task - Processing event.\r\n" );
    }
}

static void __interrupt __far vExampleInterruptHandler( void )
{
    static portBASE_TYPE xHigherPriorityTaskWoken;

 /* 先設成 false, 這樣如果有 task 被switch out時會設成ture, 才能分辨 */
    xHigherPriorityTaskWoken = pdFALSE;

    /* give semaphore */
    xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );
    if( xHigherPriorityTaskWoken == pdTRUE )
    {
        /* 做 context switch, 其中 portSWITCH_CONTEXT() 是 Open Watcom Dos 的port,
           其它平台可能有不同的 port */
        portSWITCH_CONTEXT();
    }
}

int main( void )
{
    /* 建 semaphore */
    vSemaphoreCreateBinary( xBinarySemaphore );

    /* 設定 interrupt handler */
    _dos_setvect( 0x82, vExampleInterruptHandler );

    if( xBinarySemaphore != NULL )
    {
        /* 建立要take semaphore 的 task, 它的 priority 是 3 */
        xTaskCreate( vHandlerTask, "Handler", 1000, NULL, 3, NULL );

        /* 建立定期發出 software interrupt 的 task */
        xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, 1, NULL );

        vTaskStartScheduler();
    }
    /* it should never reach here */
    for( ;; );
}

3. Counting semaphores


使用 binary semaphore 的缺點是, 在 ISR unblock binary semaphore之後, 並且讓handle task執行時, 如果這時又再有一個 interrupt, 這時候 handle task也只會做最初的那次, counting semaphore 可以解決這種情況

可以將 counting semaphore 想成是長度為n的queue, 只是我們不在乎裡面的data

通常 counting semaphore 用在兩個用途: 記錄次數, 與管理資源

底下是它的 api

xSemaphoreHandle xSemaphoreCreateCounting( unsigned portBASE_TYPE uxMaxCount,
                                           unsigned portBASE_TYPE uxInitialCount );

其中 uxMaxCount 是最大值, uxInitialCount是初始值, 成功的話回傳 semaphore handle


4. Use queue in ISR


在 ISR 裡使用 queue, 有相對應的 function:

portBASE_TYPE xQueueSendToFrontFromISR( xQueueHandle xQueue,
                                        void *pvItemToQueue
                                        portBASE_TYPE *pxHigherPriorityTaskWoken
                                        );

portBASE_TYPE xQueueSendToBackFromISR( xQueueHandle xQueue,
                                       void *pvItemToQueue
                                       portBASE_TYPE *pxHigherPriorityTaskWoken
                                       );

BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue,
                                 void *pvBuffer,
                                 BaseType_t *pxHigherPriorityTaskWoken
                                 );

這些其實就是 xQueueSendToFront(), xQueueSendToBack() 以及 xQueueReceive()

4.1 Sample code


static void vIntegerGenerator( void *pvParameters )
{
    portTickType xLastExecutionTime;
    unsigned portLONG ulValueToSend = 0;
    int i;

    /* Initialize the variable used by the call to vTaskDelayUntil(). */
    xLastExecutionTime = xTaskGetTickCount();
    for( ;; )
    {
        /* 每 200ms block */
        vTaskDelayUntil( &xLastExecutionTime, 200 / portTICK_RATE_MS );

        /* 寫入queue 5次, 這些值將從ISR讀出 */
        for( i = 0; i < 5; i++ )
        {
            xQueueSendToBack( xIntegerQueue, &ulValueToSend, 0 );
            ulValueToSend++;
        }
        /* 觸發 software interrupt 0x82 */
        vPrintString( "Generator task - About to generate an interrupt.\r\n" );
        __asm{ int 0x82 } /* This line generates the interrupt. */
        vPrintString( "Generator task - Interrupt generated.\r\n\r\n\r\n" );
    }
}

static void __interrupt __far vExampleInterruptHandler( void )
{
    static portBASE_TYPE xHigherPriorityTaskWoken;
    static unsigned long ulReceivedNumber;

    /* 宣告成 static const 確保它們不是 ISR 產生的 */
    static const char *pcStrings[] = {"St0\r\n", "St1\r\n", "St2\r\n", "St3\r\n"};
    xHigherPriorityTaskWoken = pdFALSE;

    /* Loop until the queue is empty. */
    while( xQueueReceiveFromISR( xIntegerQueue, &ulReceivedNumber, &xHigherPriorityTaskWoken ) != errQUEUE_EMPTY )
    {
        /* Mask最後兩個bit, 然後把對應的String放進queue裡 */
        ulReceivedNumber &= 0x03;
        xQueueSendToBackFromISR( xStringQueue, &pcStrings[ ulReceivedNumber ], &xHigherPriorityTaskWoken );
    }

    /* 如果寫入 queue 造成 high priority task 變成 ready, 就要做context switch */
    if( xHigherPriorityTaskWoken == pdTRUE )
    {
        /* NOTE: 這個context switch 是給 Open Watcom DOS port 使用, 其它平台有不同的port */
        portSWITCH_CONTEXT();
    }
}

static void vStringPrinter( void *pvParameters )
{
    char *pcString;
    for( ;; )
    {
        /* Block on the queue to wait for data to arrive. */
        xQueueReceive( xStringQueue, &pcString, portMAX_DELAY );
        vPrintString( pcString );
    }
}

int main( void )
{
    /* 建queue */
    xIntegerQueue = xQueueCreate( 10, sizeof( unsigned long ) );
    xStringQueue = xQueueCreate( 10, sizeof( char * ) );

    /* 設定 interrupt handler. */
    _dos_setvect( 0x82, vExampleInterruptHandler );

    /* low priority period task */
    xTaskCreate( vIntegerGenerator, "IntGen", 1000, NULL, 1, NULL );

    /* High priority interrupt handler */
    xTaskCreate( vStringPrinter, "String", 1000, NULL, 2, NULL );

    vTaskStartScheduler();
    for( ;; );
}







I2S Introduction

1. Introduction


I2S (Inter-IC Sound), 用於IC之間傳遞數位音源, 它由三條單方向線組成

  1. SCK (Serial Clock): serial transmission的clock line
  2. SD (Serial Data): serial transmission的data line
  3. WS (Word Select): 即 udio channel

在傳輸時, 有一方是Master, 取決於誰輸出SCK

Example 1: 這張圖中, Transmitter 輸出SCK, 所以 Transmitter 是 master


Example 2: 另種情況是, 雖然 receiver 從 tranmitter 的 SD 收到data, 但 receiver 輸出 SCK, 所以 receiver 是 master

Example 3: 有時候設計上會多出一個 controller, 它負責控制 SCK 以及WS, 這種情況下 controller 是 master

有時候在設計上, 為了達到master與slave角色互換, 可以利用修改pin function

在邏輯電壓準位上, 使用 TTL standard, Output Level 在 0V~0.8V為Lo, 2.4~5.0V為Hi

2 line description


2.1 Serial Data


傳輸格式為 two's complement, 並且先傳 MSB (Most Significant Bit)
使用MSB是因為如果 transmitter 與 receiver 兩邊的 word length 不同, 那麼就不需要考慮到調整word length的問題
例如, transmitter word length = 20 bits receiver word length = 24 bits, 那麼 transmitter 傳完之後, receiver 自動補上 4 個 0 就可以
另個例子是transmitter word length = 20 bits receiver word length = 16 bits, 那麼 receiver收完 16 bits 之後, 自動捨棄後面的 4 個 bits 即可


transmitter 在傳 SD 時, 它的 clock signal 在做synchronization可以是 trailing edge (high-to-low) 或者是 leading edge (low-to-high), 不管是哪一種, receiver 在 leading edge 時都得在 SD 上加 latch

2.2 Word Select


WS 輸出 0 表示 Channel 1 (Left), 輸出1表示 Channel 2 (Right)
WS在轉變0或1時, leading edge的SD是WS未轉變之前的資訊, 確定WS轉變後, 下一個leading edge 的 SD才是 WS 指定的 channel info
參考上一張圖, WS 原本值是 1 (Right Channel), 改成0之後, SD的data在1個clock period之間仍然是 Right Channel, 等到下個clock leading edge才變成 Left Channel






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