顯示具有 技術-嵌入式系統 標籤的文章。 顯示所有文章
顯示具有 技術-嵌入式系統 標籤的文章。 顯示所有文章

2018年9月23日 星期日

[Embedded] Buildroot -- 建置 gstreamer 的流程

本篇說明如何編譯gstreamer以及相關檔案。為了簡單,僅記錄使用 buildroot 的編譯步驟,如下:

 1. 安裝 buildroot
a. 安裝 buildroot,安裝過程可參考此篇  。
b. 執行下列指令,試試 buildroot 建立的image是否正確。 
$ cd output/images
$ QEMU_AUDIO_DRV=alsa qemu-system-arm -kernel zImage \ -M vexpress-a9 -m 512 -drive file=rootfs.ext2,if=sd,format=raw \ -dtb ./vexpress-v2p-ca9.dtb \ --append "root=/dev/mmcblk0 console=ttyAMA0,115200" -nographic
2. 編譯 gstreamer
a. 設定 gstreamer 選項,再次編譯
$ cd buildroot/buildroot-2017.11
$ make menuconfig
b. ToolChain 設定修改成 glib 
Toolchain -> C library -> glibc  
c. 選擇 gstreamer 
Target packages -> Audio and video applications -> gstreamer 1.x
為了方便使用,建議開啟 gst1-libav, gst1-rtsp-serve 
 
另外 gst1-plugins-base, gst1-plugins-good 也要選擇自己需要的,例如選擇rtsp, alsa, souphttpsrc, videoconvert
d. 重新編譯
$ make clean;make 
e. Test Gstreamer 
$ QEMU_AUDIO_DRV=alsa qemu-system-arm -kernel zImage \ -M vexpress-a9 -m 512 -drive file=rootfs.ext2,if=sd,format=raw \ -dtb ./vexpress-v2p-ca9.dtb \ --append "root=/dev/mmcblk0 console=ttyAMA0,115200" -nographic
$ gst-launch-1.0 -v fakesrc num_buffers=5 ! fakesink 
輸出結果如下:到達此步驟,表示編譯成功。
Pipeline is PREROLLING ...
Pipeline is PREROLLED ...
Setting pipeline to PLAYING ...
New clock: GstSystemClock
Got EOS from element "pipeline0".
Execution ended after 0:00:00.003942000
Setting pipeline to PAUSED ...
Setting pipeline to READY ...
Setting pipeline to NULL ...
Freeing pipeline ...
Setting pipeline to PAUSED ... 
若是剛剛編譯有加入 souphttpsrc則可以試試此命令 
$ gst-launch-1.0 souphttpsrc is-live=true location=http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8 ! hlsdemux ! decodebin ! videorate ! videoconvert ! ximagesink
3. 編譯成功後,若要在嵌入式平台上使用,則要注意相關 library 是否都已存在於平台內。
$ arm-linux-gnueabihf-readelf -d ./gst-launch-1.0
Tag                Type                               Name/Value
0x00000001 (NEEDED)                     Shared library: [libgstreamer-1.0.so.0]
0x00000001 (NEEDED)                     Shared library: [libgobject-2.0.so.0]
0x00000001 (NEEDED)                     Shared library: [libgmodule-2.0.so.0]
0x00000001 (NEEDED)                     Shared library: [libglib-2.0.so.0]
0x00000001 (NEEDED)                     Shared library: [librt.so.1]
0x00000001 (NEEDED)                     Shared library: [libdl.so.2]
0x00000001 (NEEDED)                     Shared library: [libpthread.so.0]
0x00000001 (NEEDED)                     Shared library: [libc.so.6]
參考資料:


2018年3月4日 星期日

2017年12月28日 星期四

[QEMU] 模擬一個實際的 ARM Linux 嵌入式系統

本篇紀錄手動自行建置系統的過程。若想盡可能的自動化進行建置,請使用 yocto 或是 buildroot。

1. 安裝 qemu
$ sudo apt-get install qemu-system-arm

2. 建立測試目錄,下載 toochain 以及 kernel
$ mkdir qemu_test
$ cd qemu_test
$ wget https://releases.linaro.org/components/toolchain/binaries/6.4-2017.11/arm-linux-gnueabihf/ gcc-linaro-6.4.1-2017.11-i686_arm-linux-gnueabihf.tar.xz
$ tar xvf gcc-linaro-6.4.1-2017.11-i686_arm-linux-gnueabihf.tar.xz
$ wget https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.14.tar.xz
$ tar xvf ./linux-4.14.tar.xz

3. 修改環境變數
$ vi ~/.bashrc
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
export PATH=$PATH:/home/albert/qemu_test/gcc-linaro-6.4.1-2017.11-i686_arm-linux-gnueabihf/bin
$ source ~/.bashrc 

4. 編譯 kernel
$ cd linux-4.14
$ vi arch/arm/configs/vexpress_defconfig
參考此篇 http://awe31402-blog.logdown.com/posts/528593 
$ make vexpress_defconfig
$ make clean;make
$ ls -al arch/arm/boot/dts/*.dtb
此時應該會出現 vexpress-v2p-ca9.dtb
$ ls -al ./arch/arm/boot/
此處應該會出現 zImage

5. 建立虛擬網卡介面
$ sudo apt-get install uml-utilities
$ sudo tunctl -t tap0 -u albert
$ sudo ifconfig tap0 192.168.7.1 netmask 255.255.255.0 promisc

6. 建立 nfs 使用的 rootfs (之前已經透過 yocto 建立,此步驟省略)

7. 執行程式
$ cd ~/qemu_test
$ QEMU_AUDIO_DRV=none qemu-system-arm -kernel ./linux-4.14/arch/arm/boot/zImage  -M vexpress-a9 -m 512 \
-dtb ./linux-4.14/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-append "/dev/nfs nfsroot=192.168.7.1:/srv/nfs/qemuarm rw ip=192.168.7.2:192.168.7.1:255.255.255.0 console=ttyAMA0,115200" \
-smp 1 -net nic,vlan=0 -net tap,vlan=0,ifname=tap0,script=no -nographic

參考資料:
http://awe31402-blog.logdown.com/posts/528593
http://daydreamer.idv.tw/rewrite.php/read-42.html




2017年5月14日 星期日

mtrace 與 libmemleak

本篇參考 https://en.wikibooks.org/wiki/Linux_Applications_Debugging_Techniques/Leaks,
測試文中兩個偵測 memory leakage 的方法,分別是 mtrace 與 libmemleak,並且紀錄測試過程。

1. 使用 mtrace
a. 針對單一程式: 
a1. 在程式碼中 #include  ,並且在需要檢視 leakage 的區間分別加入 mtrace(),muntrace(),程式碼範例可參考此連結。 
a2. 編譯以及偵測方法
$ gcc -g dleaker.c -o dleaker
$ MALLOC_TRACE=test1/mtrace.plain.log  ./dleaker
$ mtrace  ./dleaker  test1/mtrace.plain.log  > test1/mtrace.plain.leaks.log 
a3. 原理簡介
mtrace() 執行之後,會將後續所有 malloc 相關函數全部置換。並且記錄 malloc/free 使用過程。每次配置記憶體使用 "+" 紀錄,釋放記憶體使用 "-" 紀錄,最後統計沒有被釋放的記憶體,就是 memory leakage
如下: 
$ cat  mtrace.plain.log
= Start
@ ./dleaker:[0x80484cb] + 0x9dde378 0xa
@ ./dleaker:[0x80484db] + 0x9dde388 0x14
@ ./dleaker:[0x80484ec] - 0x9dde378
= End 
$ cat mtrace.plain.leaks.log
Memory not freed:
-----------------
Address     Size     Caller
0x09dde388     0x14  at /home/albert/test/libtrace/dleaker.c:13 
a4. 注意:編譯時要加入 "-g" ,這樣 mtrace 才能夠顯示出正確的檔案名稱與行號。 
b. 有時候僅知道行號,還是無法確認問題,最好是有上 backtrace,提供更清楚的呼叫流程,實驗過程如下:
b1. 替換原本的 malloc,在記憶體配置之後,打印 backtrace。
extern void *__libc_malloc(size_t size);
extern void *malloc(size_t size)
{
    void *ptr = __libc_malloc(size);
    fprintf(stderr,"%s\n", __func__);
    _ShowBacktrace();
    return ptr;
} 
b2. 一開始使用下列方法實作 _ShowBacktrace()
#include
static void _ShowBacktrace(void)
{
    void *bt[1024];
    int bt_size;
    char **bt_syms;
    int i;
    bt_size = backtrace(bt, 1024);
    bt_syms = backtrace_symbols(bt, bt_size);
    for (i = 1; i < bt_size; i++)
    {
printf("%s", bt_syms[i]);
    }
    free(bt_syms);
} 
結果程式會掛掉,因為 backtrace() 會再一次呼叫 malloc,因此此項修改會不斷的呼叫 malloc,最後導致 stack overflow。所以需要加上 flag 避免此無限迴圈。相關討論可參考 https://patchwork.ozlabs.org/patch/442890/。
若對 mtrace 內部實作有興趣,可以直接參考此檔案 http://code.metager.de/source/xref/gnu/glibc/malloc/mtrace.c


2. 使用 libmemleak.so
a. 首先自行編譯 libmemleak.so (實驗時使用 ubuntu 16.04)
$ sudo apt-get update
$ sudo apt-get install binutils-dev libiberty-dev libreadline-dev doxygen
$ wget https://github.com/CarloWood/libmemleak/releases/download/v0.1.1/libmemleak-0.1.1.tar.gz
$ tar zxvf libmemleak-0.1.1.tar.gz
$ cd libmemleak-0.1.1
$ ./configure
$ make
$ sudo make install 
b. 編譯測試範例
$ git clone https://github.com/alb423/memory_leakage.git
$ cd memory_leakage
$ make  
c. 執行測試程式,動態偵測記憶體資訊
$ export LIBMEMLEAK_SOCKNAME=./memleak_sock
$ cd ~/libmemleak-0.1.1
$ LD_PRELOAD='/usr/local/lib/libmemleak.so' ./hello
$ memleak_control 
d. 若要讓程式一開始就偵測記憶體,需加入 MEMLEAK_CONFIG 定義
$ export MEMLEAK_CONFIG=mtraceinit
$ cd ~/memory_leakage
$ LD_PRELOAD='/usr/local/lib/libmemleak.so' ./dleaker3
$ memleak_control
e.執行結果畫面截圖如下:







3. 編譯 ARM 平台上面的 libmemleak.so
先安裝 arm tool chain 
$ sudp apt-get update
$ sudp apt-get upgrade
$ sudo apt-get install gcc-arm-linux-gnueabihf 
編譯 libcurses.a
$ wget http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz
$ tar -pxzf ncurses-5.9.tar.gz
$ ./configure --host=arm-linux-gnueabihf --without-debug --without-ada --without-shared --enable-widec CPPFLAGS="-P"
$ sudo make install DESTDIR=$HOME/
註:此處編譯 library 成功,但是編譯測試程式仍然有誤,上述步驟應該有需要改善。
編譯 libreadline.so 
直接參考此篇即可,最後再安裝到相同目錄 (也許此處可以直接使用靜態編譯)  
$ sudo make install DESTDIR=$HOME/ 
編譯 libmemleak
$ CC=arm-linux-gnueabihf-gcc ./configure --host=arm-linux-gnueabihf --target=arm-linux-gnueabihf LDFLAGS="-L$HOME/usr/local/lib -L$HOME/usr/lib -lreadline -lncursesw"
$ make
$ sudo make install DESTDIR=$HOME/


Reference:
  1. https://how-to-build-for-arm.wikispaces.com/readline
  2. http://www.plunk.org/~hatch/goodies/backtracefilt.C
  3. https://github.com/CarloWood/libmemleak
  4. http://stackoverflow.com/questions/34732/how-do-i-list-the-symbols-in-a-so-file
  5. https://en.wikibooks.org/wiki/Linux_Applications_Debugging_Techniques/Leaks

2017年4月1日 星期六

TDD(Test Driven Development)

www.throwtheswitch.org 設計了一套用來作單元測試的工具。對於使用 TDD(Test Driven Development)開發流程的人員,挺方便的,其中共包含四套工具,分別是 Unity,CMock,CExpection,Ceedling。

摘錄官方說明如下:
  • Unity - Curiously Powerful Unit Testing in C for C.
  • CMock - Automagical generation of stubs and mocks for Unity Tests.
  • CException - Lightweight exception handling for C.
  • Ceedling - Test build management.
以下將針對這幾個工具稍做介紹。

2017年2月27日 星期一

偵測 Race Condition 的方法

在並行運算領域,哲學家就餐問題(英語:Dining philosophers problem)是一個經典問題,此問題可能會導致死結(deadlock)或飢餓(starvation)。

常見的解決方式有服務生解法,資源分級解法,Chandy/Misra解法。細節可參考 wiki 的 哲學家就餐問題。

解決此問題的基本概念,便是讓共享的資源在不同執行緒內可以受到控制,但有時因為設計不良,便會出現競爭危害(race hazard)。又稱為競爭條件(race condition)

本篇將針對 race condition 的偵測及解決方式作一介紹。

2017年2月25日 星期六

Cross compile valgrind for ARM-Linux

紀錄一下編譯 valgrind 的過程,也許有天會用得到。

1. Download valgrind
使用的版本是 valgrind 3.12.0 -- 20 October 2016.
可從此處下載 http://www.valgrind.org/downloads/valgrind-3.12.0.tar.bz2

2. 編譯程式
$ wget http://www.valgrind.org/downloads/valgrind-3.12.0.tar.bz2
$ tar jxvf ./valgrind-3.12.0.tar.bz2
$ cd valgrind-3.12.0/
先修改目錄下的 configure, 尋找 armv7, 取代成 arm 
$ CC=arm-none-linux-gnueabi-gcc  ./configure --host=arm-none-linux-gnueabi --target=arm-none-linux-gnueabi --prefix=$HOME/test/valgrind  CFLAGS=-static
$ make
$ make install

3. 確認檔案編譯正確,並用 qemu-arm 檢查檔案是否可以正確執行
$ file valgrind
valgrind: ELF 32-bit LSB  executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 2.6.16, not stripped
4. 若安裝路徑有問題,可以使用下列方式安裝
./configure --prefix=/target/board/path
make install DESTDIR=/local/path
Reference:

2016年11月9日 星期三

epoll 介紹

最近把 C10K 問題重新溫習一遍,針對裡面提到的幾個技術,有些時間好好看一下。第一個注意到的就是 linux 系統下的 epoll。

epoll 與 select 的差異

  • select() 有個問題,其最大個數限制 FD_SETSIZE,預設值可能是 1024 或 2048
  • epoll 上限是系統最大可以打開文件的數目,執行 "cat /proc/sys/fs/file-max" 可檢視此上限值。在 Linux 2.6.28之後,要檢視 /proc/sys/fs/epoll/max_user_watches 
  • 另外 epoll 透用 mmap 讓 kernel space 與 user space 共享同一塊內存,減少內存拷貝。而 select() 則需要較多的內存拷貝。

epoll 支援兩種工作模式
LT(level triggered)
支援 block 和 no-block socket,在事件狀態未變更前將不斷被觸發。(直到緩衝區資料全部被讀取)
ET(edge-triggered)
僅支援 no-block socket,在事件狀態改變時只觸發一次。

三個API
int epoll_create(int maxfds);
建立 epoll 文件描述符 
int epoll_ctl (int epfd, int op, int fd, struct epoll_event *event);
增刪或修改文件描述符,這些描述符的集合又稱為 epoll set。
op 可以是 EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD 
epoll_event 結構如下:
struct epoll_event
{
  uint32_t events; /* Epoll events */
  epoll_data_t data; /* User data variable */
}; 
新增 event 時要設定事件以及文件描述符,如下: 
setnonblocking(fd);
ev.events=EPOLLIN|EPOLLET;
ev.data.fd=fd; 
// EPOLLET:工作模式設定為 edge-triggered,若不設定則是 level triggered
// EPOLLONESHOT: 一個事件發生並且讀取之後,不再監聽此文件描述符
// EPOLLIN是監聽讀的事件,若需要監聽寫的事件,可加入EPOLLOUT
int epoll_wait(int epfd, epoll_event *events, int max events, int timeout)
等待用戶感興趣的 IO事件發生, 若有事件發生會返回事件個數,並將事件放入 events。若要關閉 epoll 文件描述符, 直接使用 close() 即可。

程式範例
epollfd = epoll_create1(0);

event.events = EPOLLIN;
event.data.fd = serverfd;
epoll_ctl(epollfd, EPOLL_CTL_ADD, serverfd, &event);

while(true) {
 nfds  = epoll_wait(epollfd, &events, MAXEVENTS, timeout);
 for(i = 0; i < nfds; ++i) {
  if(events[i].data.fd == serverfd) {
   conn_sock = accpet(listen_sock,(struct sockaddr *) &addr, &addrlen);
   setnonblocking(conn_sock);                       
   
   ev.events = EPOLLIN | EPOLLET;
            ev.data.fd = conn_sock;
            if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev) == -1) {
    perror("epoll_ctl: conn_sock");
    exit(EXIT_FAILURE);
   }
   
  } else {
   do_use_fd(events[n].data.fd);
  }
 }
}


參考資料

iNotify 筆記

iNotify 可用來監控目錄或是檔案變化,包含 read/write/add/delete 等等,Linux 2.6.13 之後導入此機制。可以用來判斷是否有人將惡意軟體下載到某個目錄(例如:mirai),或是與rsync結合,當作 Linux 系統的檔案同步機制。

此篇記錄此函數的使用方法。

iNotify 共提供三個 API
  • int inotify_init(void)
  • int inotify_add_watch(int fd, const char* pathname, int mask)
  • int inotify_rm_watch(int fd, int wd)
inotify_init() 初始化完成後,可得到一個file descriptor。
該descriptor 可經由 read()、select()、epoll來操作,與多數的file descriptor無異,同時支援blocking、non-blocking IO。
當有任何事件觸發時,系統會將事件寫入在 inotify_event 結構中,供使用者存取
struct inotify_event {
int      wd;       /* Watch descriptor */
uint32_t mask;     /* Mask of events */
uint32_t cookie;   /* Unique cookie associating related events */
uint32_t len;      /* Size of name field */
char     name[];   /* Optional null-terminated name */
};

程式範例
#define EVENT_SIZE  ( sizeof (struct inotify_event) )
#define BUF_LEN     ( 1024 * ( EVENT_SIZE + 16 ) )

int fd, wd, length, i = 0;
char buffer[BUF_LEN];

fd = inotify_init();
wd = inotify_add_watch( fd, "/home/strike", 
      IN_MODIFY | IN_CREATE | IN_DELETE );
length = read( fd, buffer, BUF_LEN );  // blocked before event occurs
while ( i < length ) {
 struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];
 if ( event->len ) {
  if ( event->mask & IN_CREATE ) { // created
  }
  else if ( event->mask & IN_DELETE ) { // deleted
  }
  else if ( event->mask & IN_MODIFY ) { // modified
  }
 }
 i += EVENT_SIZE + event->len;
}

inotify_rm_watch( fd, wd );
close( fd );


參考資料:
  1. http://www.ibm.com/developerworks/cn/linux/l-ubuntu-inotify/
  2. http://man7.org/linux/man-pages/man7/inotify.7.html

2016年11月6日 星期日

Mirai程式碼分析

Mirai程式碼分析

Mirai 是一網路殭屍病毒,名稱可能是取自日文「未来」,最近作者Anna-senpai開放原始碼,正好可以一窺殭屍病毒作法。本篇針對其程式流程做一介紹,希望在開發自家產品時,能夠避開這些攻擊方法,避免成為殭屍大軍一員。

2016年7月20日 星期三

[Embedded] U-Boot的開機流程


以 U-Boot source code 為例,整理 embedded system 的開機流程

1.  開機流程
CPU上電後,會到固定位置讀取指令並且執行,不同架構其位置不同,比方說 ARM 結構的 CPU 會從位址 0x00000000 開始執行,MIPS結構的 CPU 會從位址 0xBFC00000 開始,。 
若要手動搞定一個嵌入式系統,首先要作的便是安排系統記憶體的編排方式,透過編寫 linker script,可以控制 CPU 上電後會先開始執行的指令,因而讓開機時便開始執行 U-Boot。 
以下是一個 linker script 例子,此例中,會將 start.o 擺放在 0x00000000 的位置。系統開機時便會在從此處開始取出指令開始執行。一般作法在 start.o 會做些硬體的初始化,然後再執行 u-boot.  (此例子取自 u-boot-2013.04-rc1.tar.bz2 ,參考\board\samsung\smdk6400\u-boot-nand.ld)

2.  U-Boot 有兩種工作模式
(1)啟動載入(Boot loading)模式。
上電後,Bootloader 從板子上的某個固態存放裝置上將作業系統載入到RAM中運行。
(2)下載(Downloading)模式。
在這種模式下,開發人員可以使用各種命令,(xmodem, tftp, usb and etc...)通過串口連接或網路連接等通信手段從主機(Host)下載檔案(比如內核映射、檔案系統映射),將它們直接放在記憶體運行或是燒入Flash類固態存放裝置中。

3. U-Boot啟動過程分為兩個階段
a. Phase 1,
硬體設備初始化, 準備 RAM 空間(對DDR晶片初始化), 設置 CPU 工作模式為管理模式(SVC)複製 U-Boot程式至 RAM, 啟動 MMU, 設置堆疊,  跳轉到 phase2的程式碼進入點. 
b. Phase 2,
初始化要使用的硬體設備, 檢測系統記憶體映象 (memory map), 指定初始化函數清單(如:cpu_init, interrupt_init),為核心設定啟動參數, 進入 U-Boot 命令列 (無窮迴圈)

4.從 U-Boot 程式碼分析其流程

start.s (程式碼及註解請看這裡)
禁止 FIQ, IRQ中斷,並將 ARM CPU 設定為 SVC(Supervisor Calls) 模式 
設定 Cache和 MMU
若是從 nand boot,則應該已經做了硬體的Reset,因此不需清除Cache與暫停MMU。否則要 
  • 設定 Cache,此時應該先讓 ICache, DCache, TLB 都失效。
  • 設定 MMU,包含 Endian, Address Align Check, 並通知 MMU 不要使用 ICache, DCache。
  • 設定CPU內的記憶體 TCM(Tightly-coupled memory),此部分也是可分為 Instruction TCM, Data TCM
設定週邊設備(peripheral)對應的記憶體位址 
執行 lowlevel_init()
此函數用來初始化板子相關設定,程式碼會放在 board 目錄下,例如:board/samsung/smdk6400/lowlevel_init.s,此函數會設定LED,WatchDog,Interrupt,System Clock,Serial Port,NAND Flash,DRAM。建置一個小的 Stack,清除BSS段。 
執行 _main() 
此函數用來建立C語言的執行環境,執行結束之後會返回 start.s。實作可參考 crt0.s。
relocate code
將 ELF 對應的各個區塊,複製到記憶體內。然後啟動MMU,致能TLB。此時還不需要 ICache, DCache,因此不用變更設定。 
最後設定 exception handler,處理異常事件

crt0.s (C-runtime startup Code,編譯成 Thumb code),程式碼位置在 arch/arm/lib/crt0.s,主要動作如下
配置一小塊堆疊,用來放置 160bytes 的 Global Data,將此記憶體位置紀錄在 r8,供後續使用。 
呼叫 board_init_f(ulong bootflag),此函數是U-Boot執行的第一個 C 函數,程式碼在arch\arm\lib\board.c,此函數會清空 Global Data,將 init_sequence[] 內定義的所有函數都執行一遍,其初始化工作可能包含 serial, baudrate, consol, system information, i2c 跟 dram,並規劃SDRAM空間配置,由位址最大處往前,分別分配 TLB, .Bss, .Data, .Text等區段,接著再分配 malloc空間,global data, abort_stack。

如果是 non-SPL build,會繼續下列流程
呼叫 relocate_code(addr_sp, gd, addr_moni),跳回到 start.S中,此時會將 u-boot 程式碼複製到 SDRAM中。複製成功後,執行 bx lr,跳回 crt0.S。 
呼叫 c_runtime_cpu_setup,此時一樣會跳至 start.S,然後立刻透過 bx lr 返回 crt0.S。 
清空BSS,控制LED閃爍。
呼叫 board_init_r(gd_t *id, ulong dest_addr),此函數是 U-Boot 執行的第二個 C函數,也是做些初始化動作,注意的是許多外接設備都在此時才初始化,如 :Ethernet, I2Cfast, LCD, KEYBOARD, USB, SCSI, KGDB等, 最後會進入一個無窮迴圈裡,持續的執行 main_loop(), 
main_loop()的主要作用就是 U-Boot 啟動管理,此時可能會 delay 數秒 (CONFIG_BOOTDELAY),等待使用者輸入指令。main_loop()的實作可參考 common/main.c。此函數會取得環境參數中的 bootcmd,執行 bootcmd 載入linux kernel,後續任務便由 kernel 接手。 
可透過 bootm 命令載入 kernel,例如:"bootm 0x80700000"。
cmd_bootm.c
設定 kernel 的 entry point 
取出 zImage 所儲存的 64 bytes header,判斷 magic number是否正確? 
設定 ramdisk 
取得 fdt (flattened device tree),設定各項周邊設備。 
執行 kernel,函數宣告為 void (*theKernel) (int, char **, char **, int *);,此函數內容還需要再研究。

以ARM開機為例,做個簡單總結,CPU開機時會取出 0x00000000 處的程式碼 (start.s),初始化相關設備,並準備好C語言執行環境,將 u-boot 程式 relocate 至 SDRAM,執行 u-boot 程式,初始化所有設備,最後由 u-boot 傳入各項參數,啟動 kernel。

2016年6月8日 星期三

好碼共享

這裡會收集一些實用的程式寫法。

1. hex2bin: 將字串 "F9" 轉成數值 0xF9 (取自 rtmpdump)
#define HEX2BIN(a)      (((a)&0x40) ? ((a)&0xf) + 9:  ((a)&0xf)) //這個轉換方式超棒
int hex2bin(char *str, char **hex)
{
  char *ptr;
  int i, l = strlen(str);
  if (l & 1)
  return 0;
  *hex = malloc(l/2);
  ptr = *hex;
  if (!ptr)
    return 0;
  for (i=0; i    *ptr++ = (HEX2BIN(str[i]) << 4) | HEX2BIN(str[i+1]);
  return l/2;
}

2. linker script 內的 ALIGN 作法
#define ALIGN(exp)   (. + exp - 1) & ~(exp - 1)
// Return the result of the current location counter (.) aligned to the next exp boundary. exp must be an expression whose value is a power of two. 
3. linux kernel.h 內的幾個 Macro
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
#define container_of(ptr, type, member) ({    \
const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
(type *)( (char *)__mptr - offsetof(type,member) );})
#define min(x, y) ({    \
typeof(x) _min1 = (x);    \
typeof(y) _min2 = (y);    \
(void) (&_min1 == &_min2);    \ /* type check*/
_min1 < _min2 ? _min1 : _min2; }) 
此處可參考 http://stackoverflow.com/questions/5595593/min-macro-in-kernel-h

4. 建議配置記憶體的方法 p = kmalloc(sizeof(*p), ...); ,此作法好處是好處是萬一pointer p的型別改變時,不會因為忘記同步更該型別的名稱而出錯。

5. TBD

2016年6月3日 星期五

[Embedded] 檢視 message queue 資訊

Linux 下的 IPC 運作若使用 message queue,有兩種 message queue 可用,分別是 SYSTEM V MESSAGE QUEUE 與 POSSIX MESSAGE QUEUE,本篇將介紹如何確認 message queue 中的資訊。

2016年3月9日 星期三

[Embedded] how to check heap corruption

上週的 linux news 提到 iOS 上的一個問題,Analysis of iOS & OS X Vulnerability: CVE-2016-1722,指出了 heap overflow 的問題。這篇新聞提醒了自己,應該要將之前的實戰經驗做一下整理了。本篇會針對如何找到 heap corruption 的方法作一整理。

2016年2月4日 星期四

[Openalpr] How to cross compilation openalpr for ARM?

Recently, I survey the open source of license plate recognition. The project "openalpr" seems a good option. I reference the Compilation-instructions-(Ubuntu-Linux),  and try to run the program on arm based linux. Below is my memo.

2015年11月3日 星期二

[Embedded] Arduino RFID 模組

前陣子到光華商場買了一個 RFID 模組,今晚比較有空,便來玩玩看,作個記錄。

測試設備
  • Arduino UNO 
  • RFID-RC522,一組$280,含兩個 RFID Card 和 RFID Reader

2015年6月1日 星期一

[Embedded] 利用 gdb script 實作 strcmp 函數

要在 gdb 內直接使用條件式中斷(Conditional breakpoint),例如:當某個字串出現時程式才進入中斷點,應該怎麼做呢?

1. 可以使用 commands 命令來設定條件式中斷
break foo
commands
if x>0
continue
else
printf "x is %d\n",x
end
end

2. 問題在於如何比較字串,以下整理網路上找到的幾種作法。若gdb版本較舊,則可能需要找尋其他方法。

方法一:使用 strcmp
(gdb) set $x = malloc(strlen("foobar") + 1)(gdb) call strcpy($x, "foobar")(gdb) break a_leg if strcmp(foo, $x) == 0

方法二:透過 python
可以直接使用下列函數 (Convenient functions) 
$_memeq(buf1, buf2, length)Returns one if the length bytes at the addresses given by buf1 and buf2 are equal. Otherwise it returns zero.  
$_regex(str, regex)Returns one if the string str matches the regular expression regex. Otherwise it returns zero. The syntax of the regular expression is that specified by Python's regular expression support.  
$_streq(str1, str2)Returns one if the strings str1 and str2 are equal. Otherwise it returns zero. 




也可以自行實作 strcmp
(gdb) define strcmp>py print cmp(gdb.execute("output $arg0", to_string=True).strip('"'), $arg1)>end(gdb) strcmp $x "hello"0

針對舊版本的 gdb,例如我所使用的 gdb 7.1,並不支援上述函數,因此需要自行實作一個 conevenient function,範例如下: 
py
class MyStrcmp (gdb.Function):
    """My Own Strcmp"""

    def __init__ (self):
        super (MyStrcmp, self).__init__ ("mystrcmp")

    def invoke (self, arg0, arg1):
        print "input '" + arg0.string() + "' and '" + arg1.string() + "'"
        if arg0.string() == arg1.string() :
            print "equal"
            return 0
        else:
            print "not equal"  
            return 1
MyStrcmp()
end
用法: 
print $mystrcmp("hello", "hello")

方法三:使用 gdb script 實作 strcmp
set var  $_isEq=0

# Yes! GDB_STRCMP, below, is a gdb function.
# Function that provides strcmp-like functionality for gdb script;
# this function will be used to match the password string provided in command line argument
# with the string argument of strcmp in program
define GDB_STRCMP
set var  $_i=0
set var  $_c1= *(unsigned char *) ($arg0 + $_i)
set var  $_c2= *(unsigned char *) ($arg1 + $_i)
while (  ($_c1 != 0x0) && ($_c2 != 0x0) && ($_c1 == $_c2) )

#printf "\n i=%d, addr1=%x(%d,%c), addr2=%x(%d,%c)", $_i, ($arg0 + $_i),$_c1, $_c1, ($arg1 + $_i), $_c2,$_c2
set  $_i++
set  $_c1= *(unsigned char *) ($arg0 + $_i)
set  $_c2= *(unsigned char *) ($arg1 + $_i)

#while end
end

if( $_c1 == $_c2)
set $_isEq=1
else
set $_isEq=0
end

#GDB_STRCMP end
end

Reference:

  1. https://sourceware.org/gdb/onlinedocs/gdb/Break-Commands.html
  2. http://stackoverflow.com/questions/13961368/conditional-breakpoint-using-strcmp-in-gdb-on-mac-os-x-conflicts-with-objectiv
  3. https://sourceware.org/gdb/current/onlinedocs/gdb/Convenience-Funs.html
  4. http://stackoverflow.com/questions/7423577/how-to-compare-a-stored-string-variable-in-gdb
  5. http://www.opensourceforu.com/2011/09/modify-function-return-value-hack-part-2/

2015年3月28日 星期六

[Embedded] 使用 gdbserver 時,出現 armv5te 警告訊息

問題描述:使用 gdbserver 追蹤程式時,target 為 armv6 架構,但卻出現 armv5te 的警告訊息。

問題原因:我所使用的 tool chain arm-2009q3,預設會假設 target 為 armv5te架構

解決方法:

1. 先登入 Target 端 console,確認 cpu info 

1 # cat /proc/cpuinfo
2 Processor : ARMv6-compatible processor rev 5 (v6l)
3 BogoMIPS : 526.25
4 Features : swp half fastmult edsp java
5 CPU implementer : 0x41
6 CPU architecture: 6TEJ
7 CPU variant : 0x1
8 CPU part : 0xb36
9 CPU revision : 5
10
11 Hardware : Coconut
12 Revision : 13ec3011
13 Serial : 0000000000000000

2. 確認目前 arm-none-linux-gnueabi-gcc 的預設架構

1 $ arm-none-linux-gnueabi-gcc -Q --help=target
2 The following options are target specific:
3 -falign-arrays [disabled]
4 -mabi=
5 -mabort-on-noreturn [disabled]
6 -mapcs [disabled]
7 -mapcs-float [disabled]
8 -mapcs-frame [disabled]
9 -mapcs-reentrant [disabled]
10 -mapcs-stack-check [disabled]
11 -march= armv5te
12 -marm [enabled]
13 -mbig-endian [disabled]
14 -mcallee-super-interworking [disabled]
15 -mcaller-super-interworking [disabled]
16 -mcirrus-fix-invalid-insns [disabled]
17 -mcpu=
18 -mfix-cortex-m3-ldrd [enabled]
19 -mfix-janus-2cc [disabled]
20 -mfloat-abi=
21 -mfp16-format=
22 -mfp=
23 -mfpe [disabled]
24 -mfpe=
25 -mfpu=
26 -mglibc [enabled]
27 -mhard-float [disabled]
28 -mlittle-endian [enabled]
29 -mlong-calls [disabled]
30 -mlow-irq-latency [disabled]
31 -mmarvell-div [disabled]
32 -mpic-register=
33 -mpoke-function-name [disabled]
34 -msched-prolog [enabled]
35 -msingle-pic-base [disabled]
36 -msoft-float [disabled]
37 -mstructure-size-boundary=
38 -mthumb [disabled]
39 -mthumb-interwork [enabled]
40 -mtp=
41 -mtpcs-frame [disabled]
42 -mtpcs-leaf-frame [disabled]
43 -mtune=
44 -muclibc [disabled]
45 -mvectorize-with-neon-quad [disabled]
46 -mword-relocations [disabled]
47 -mwords-little-endian [disabled]
很明顯,若使用預設值 armv5te進行編譯,則 arm 架構不符。
3.  如何確認現在正在使用的 gdbserver 其適用的arm架構呢?
$arm-none-linux-gnueabi-readelf -A ./gdbserver
Attribute Section: aeabi
File Attributes
  Tag_CPU_name: "5TE"
  Tag_CPU_arch: v5TE
  Tag_ARM_ISA_use: Yes
  Tag_THUMB_ISA_use: Thumb-1
  Tag_ABI_PCS_wchar_t: 4
  Tag_ABI_FP_denormal: Needed
  Tag_ABI_FP_exceptions: Needed
  Tag_ABI_FP_number_model: IEEE 754
  Tag_ABI_align8_needed: Yes
  Tag_ABI_align8_preserved: Yes, except leaf SP
  Tag_ABI_enum_size: int

4 因此在重新編譯 gdbserver 時應該要指定 armv6,舉例如下:
~/gdb/gdb-7.9/gdb/gdbserver$./configure --build=i686-pc-linux-gnu --host=arm-none-linux-gnueabi  --target=arm-none-linux-gnueabi CROSS_COMPILE=arm-none-linux-gnueabi- CFLAGS='-g -O2 -march=armv6  -mtune=arm1136j-s'
編譯方式可參考此篇 。是否正確編譯成功,可透過 readelf 來檢視,如下: 
&arm-none-linux-gnueabi-readelf -A ./gdbserver
Attribute Section: aeabi
File Attributes
  Tag_CPU_name: "6"
  Tag_CPU_arch: v6
  Tag_ARM_ISA_use: Yes
  Tag_THUMB_ISA_use: Thumb-1
  Tag_ABI_PCS_wchar_t: 4
  Tag_ABI_FP_denormal: Needed
  Tag_ABI_FP_exceptions: Needed
  Tag_ABI_FP_number_model: IEEE 754
  Tag_ABI_align8_needed: Yes
  Tag_ABI_align8_preserved: Yes, except leaf SP
  Tag_ABI_enum_size: int

5. 那麼 arm 架構不符會發生什麼問題呢?
主要是編譯成機器語言時,可能會使用不同的instruction set或call convention,可能造成執行時的錯誤。其中差異可以參考此篇討論。
6. 編譯 gdb
編譯方式如下
$./configure --target=arm-none-linux-gnueabi
$ make 
不過這樣編譯出來的 gdb,在進行遠端除錯時,會出現以下錯誤訊息
"warning: Can not parse XML target description; XML support was disabled at compile time" 
手動安裝 expat,重新編譯gdb便可解決,步驟如下:
可從此處下載原始碼 http://sourceforge.net/projects/expat/files/latest/download
$ tar zxvf expat-2.1.0.tar.gz
$ cd expat-2.1.0
$ ./configure
$ make;make install 
另外支援 python script 會方便許多,因此我個人偏好使用下列這個編譯方式
$ sudo apt-get install python2.7-dev
$./configure --target=arm-none-linux-gnueabi --with-expat --with-python
$ make
註:若編譯的是 gdb-7.1,其 python 支援的功能是有限制的
http://stackoverflow.com/questions/8986589/how-to-get-output-from-gdb-execute-in-pythongdb-gdb-7-1

7. 若不使用 gdbserver,要直接編譯一個在 target system 執行的 gdb,需要先編譯 termcap,可參考下列設定

Build termcap 
./configure --build=i686-pc-linux-gnu --host=arm-linux-gnueabihf --target=arm-linux-gnueabihf CROSS_COMPILE=arm-linux-gnueabihf- --prefix=/home/albert/tools/termcap
Build gdb 
./configure --build=i686-pc-linux-gnu --host=arm-none-linux-gnueabi --target=arm-none-linux-gnueabi CROSS_COMPILE=arm-none-linux-gnueabi- CFLAGS='-g -O2 -I/home/albert/tools/termcap/include' LDFLAGS='-static -L/home/albert/tools/termcap/lib' CPPFLAGS='-I/home/albert/tools/termcap/include'

Reference:
http://ftp.gnu.org/gnu/gdb/ 
https://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html
https://lists.debian.org/debian-arm/2011/11/msg00043.html
http://blog.csdn.net/a_ran/article/details/38404483
http://www.360doc.com/content/12/0312/23/532901_193884753.shtml
http://www.360doc.com/content/12/0312/23/532901_193884753.shtml
http://stackoverflow.com/questions/4381102/differences-between-arm-architectures-from-a-c-programmers-perspective
http://www.it.uom.gr/teaching/gcc_manuals/onlinedocs/gdb_35.html
http://lists.gnu.org/archive/html/bug-gnu-emacs/2001-11/msg00579.html