Bagaimana cara berhenti menulis firmware untuk mikrokontroler dan mulai hidup


Halo, nama saya Eugene, dan saya bosan menulis firmware untuk mikrokontroler. Bagaimana ini terjadi dan apa yang harus dilakukan dengan itu, mari kita cari tahu.


Setelah Anda bekerja dalam pemrograman besar C ++, Java, Python, dll., Anda tidak ingin kembali ke mikrokontroler yang kecil dan buncit. Untuk sedikit alat dan perpustakaan mereka. Namun terkadang tidak ada yang bisa dilakukan, tugas real-time dan otonomi tidak meninggalkan pilihan. Tetapi ada beberapa jenis tugas yang menjadi liar di daerah ini untuk dipecahkan.


Misalnya, peralatan pengujian, sesuatu yang lebih membosankan dan pelajaran membosankan dalam pemrograman tertanam, sulit dibayangkan. Secara umum, serta alat yang nyaman untuk ini. Anda menulis ... Anda berkedip ... Anda berkedip ... LED (kadang-kadang masuk di UART). Semua pena, tanpa alat pengujian khusus.


Ini juga menyedihkan bahwa tidak ada tes instrumental untuk mikrokontroler kecil kami. Semuanya hanya melalui firmware dan melalui debugger untuk menguji.


Dan studi bekerja dengan perangkat dan periferal baru membutuhkan banyak usaha dan waktu. Satu kesalahan dan program harus dikompilasi ulang dan dijalankan kembali setiap kali.


Untuk eksperimen semacam itu, sesuatu seperti REPL lebih cocok, sehingga Anda dapat dengan mudah dan tanpa rasa sakit melakukan hal-hal ini, setidaknya sepele, hal-hal:


\


Bagaimana caranya, seri artikel ini dikhususkan.


Dan kali ini saya menemukan sebuah proyek di mana perlu untuk menguji perangkat yang agak rumit, dengan banyak semua jenis sensor dan chip lain yang tidak saya kenal sebelumnya, yang menggunakan banyak perangkat MK dan banyak antarmuka yang berbeda. Kegembiraan khusus adalah bahwa saya tidak memiliki kode sumber firmware untuk papan tulis, sehingga semua tes harus ditulis dari awal, tanpa menggunakan waktu pengoperasian dari kode sumber.


Proyek menjanjikan juru roti yang baik dan kontes menarik selama dua bulan atau lebih (dan kemungkinan besar lebih).


Oke, kita tidak akan menangis. Seseorang harus terjun ke belantara C dan firmware tak berujung lagi, atau menolak atau datang dengan sesuatu untuk memfasilitasi pelajaran ini. Pada akhirnya, kemalasan dan keingintahuan adalah mesin kemajuan.


Terakhir kali, ketika saya mengerti OpenOCD, saya menemukan titik yang menarik dalam dokumentasi


http://openocd.org/doc/html/General-Commands.html 15.4 Memory access commands mdw, mdh, mdb —         mww, mwh, mwb —        

Menarik ... Dan apakah mungkin untuk membaca dan menulis register periferal dengan mereka? .. ternyata memungkinkan, dan di samping itu, perintah-perintah ini dapat dijalankan dari jarak jauh melalui server TCL, yang dimulai ketika openOCD dimulai.


Berikut adalah contoh LED yang berkedip untuk stm32f103C8T6


 // Step 1: Enable the clock to PORT B RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // Step 2: Change PB0's mode to 0x3 (output) and cfg to 0x0 (push-pull) GPIOC->CRH = GPIO_CRH_MODE13_0 | GPIO_CRH_MODE13_1; // Step 3: Set PB0 high GPIOC->BSRR = GPIO_BSRR_BS13; // Step 4: Reset PB0 low GPIOC->BSRR = GPIO_BSRR_BR13; 

dan urutan serupa dari perintah openOCD


 mww 0x40021018 0x10 mww 0x40011004 0x300000 mww 0x40011010 0x2000 mww 0x40011010 0x20000000 

Dan sekarang, jika Anda berpikir tentang yang abadi dan mempertimbangkan firmware untuk MK ... maka tujuan utama dari program ini adalah untuk menulis ke register chip; Firmware yang hanya akan melakukan sesuatu dan bekerja hanya dengan inti prosesor tidak memiliki penggunaan praktis!


Catatan

Meskipun tentu saja Anda dapat mempertimbangkan crypt (=


Banyak yang akan lebih ingat tentang bekerja dengan interupsi. Tetapi mereka tidak selalu diperlukan, dan dalam kasus saya, Anda dapat melakukannya tanpa mereka.


Jadi, hidup menjadi lebih baik. Di sumber openOCD Anda bahkan dapat menemukan contoh menarik menggunakan antarmuka ini.


Sangat bagus kosong di python.


Sangat mungkin untuk mengkonversi alamat register dari file header, dan mulai menulis dalam bahasa scripting halal. Anda sudah dapat menyiapkan sampanye, tetapi bagi saya rasanya tidak cukup, karena saya ingin menggunakan Perpustakaan Periferal Standar atau HAL baru untuk bekerja dengan periferal alih-alih sibuk dengan register.


Memindahkan pustaka ke python ... dalam mimpi buruk kita akan melakukannya. Jadi, Anda perlu menggunakan perpustakaan ini di C atau ... C ++. Dan di pro, Anda dapat mengganti hampir semua operator ... untuk kelas mereka.


Dan alamat dasar dalam file header, ganti dengan objek dari kelas mereka.


Misalnya, dalam file stm32f10x.h


 #define PERIPH_BB_BASE ((uint32_t)0x42000000) /*!< Peripheral base address in the bit-band region */ 

Ganti dengan


 class InterceptAddr; InterceptAddr addr; #define PERIPH_BB_BASE (addr) /*!< Peripheral base address in the bit-band region */ 

Tapi game dengan pointer di perpustakaan memotong ide ini sejak awal ...


Berikut adalah contoh file stm32f10x_i2c.c:


 FlagStatus I2C_GetFlagStatus(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG) { __IO uint32_t i2creg = 0, i2cxbase = 0; …. /* Get the I2Cx peripheral base address */ i2cxbase = (uint32_t)I2Cx; …. 

Jadi, perlu untuk mencegat alamat ke alamat entah bagaimana berbeda. Cara melakukan ini mungkin patut dilihat di Valgrind, bukan tanpa alasan bahwa ia memiliki seorang memchecker. Yah, dia benar-benar harus tahu cara mencegat alamat.


Ke depan, saya akan mengatakan bahwa lebih baik tidak melihat ke sana ... Saya hampir berhasil mencegat panggilan ke alamat. Untuk hampir semua kasus kecuali ini


 Int * p = ... *p = 0x123; 

Dimungkinkan untuk mencegat alamat, tetapi tidak mungkin lagi untuk mencegat data yang direkam. Hanya nama register internal tempat nilai ini berada, tetapi yang tidak dapat dijangkau dari memcheck.


Bahkan, Valgrind mengejutkan saya, di dalam monster libVEX kuno digunakan, tentang yang saya tidak menemukan informasi di Internet. Bagus bahwa sedikit dokumentasi ditemukan di file header.


Lalu ada alat DBI lainnya.


Frida, Dynamic RIO, beberapa lagi, dan akhirnya mendapat Pintool.


PinTool memiliki beberapa dokumentasi dan contoh yang cukup bagus. Meskipun saya masih belum cukup, saya harus melakukan eksperimen dengan beberapa hal. Alat ini ternyata sangat kuat, hanya mengganggu kode yang ditutup dan pembatasan hanya untuk platform intel (meskipun di masa depan ini dapat dielakkan)


Jadi, kita perlu mencegat menulis dan membaca di alamat tertentu. Mari kita lihat instruksi apa yang bertanggung jawab untuk https://godbolt.org/z/nJS9ci ini.


Untuk x64, ini akan menjadi MOV untuk kedua operasi.


Dan untuk x86 itu adalah MOV untuk menulis dan MOVZ untuk membaca.


Catatan: yang terbaik adalah tidak mengaktifkan optimasi, jika tidak instruksi lain mungkin muncul.


Judul spoiler
 INS_AddInstrumentFunction(EmulateLoad, 0); INS_AddInstrumentFunction(EmulateStore, 0); ..... static VOID EmulateLoad(INS ins, VOID *v) { // Find the instructions that move a value from memory to a register if ((INS_Opcode(ins) == XED_ICLASS_MOV || INS_Opcode(ins) == XED_ICLASS_MOVZX) && INS_IsMemoryRead(ins) && INS_OperandIsReg(ins, 0) && INS_OperandIsMemory(ins, 1)) { INS_InsertCall(ins, IPOINT_BEFORE, AFUNPTR(loadAddr2Reg), IARG_MEMORYREAD_EA, IARG_MEMORYREAD_SIZE, IARG_RETURN_REGS, INS_OperandReg(ins, 0), IARG_END); // Delete the instruction INS_Delete(ins); } } static VOID EmulateStore(INS ins, VOID *v) { if (INS_Opcode(ins) == XED_ICLASS_MOV && INS_IsMemoryWrite(ins) && INS_OperandIsMemory(ins, 0)) { if (INS_hasKnownMemorySize(ins)) { if (INS_OperandIsReg(ins, 1)) { INS_InsertCall(ins, IPOINT_BEFORE, AFUNPTR(multiMemAccessStore), IARG_MULTI_MEMORYACCESS_EA, IARG_REG_VALUE, INS_OperandReg(ins, 1), IARG_END); } else if (INS_OperandIsImmediate(ins, 1)) { INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)multiMemAccessStore, IARG_MULTI_MEMORYACCESS_EA, IARG_UINT64, INS_OperandImmediate(ins, 1), IARG_END); } } else { if (INS_OperandIsReg(ins, 1)) { INS_InsertCall(ins, IPOINT_BEFORE, AFUNPTR(storeReg2Addr), IARG_MEMORYWRITE_EA, IARG_REG_VALUE, INS_OperandReg(ins, 1), IARG_MEMORYWRITE_SIZE, IARG_END); } else if (INS_OperandIsImmediate(ins, 1)) { INS_InsertCall(ins, IPOINT_BEFORE, AFUNPTR(storeReg2Addr), IARG_MEMORYWRITE_EA, IARG_UINT64, INS_OperandImmediate(ins, 1), IARG_UINT32, IARG_MEMORYWRITE_SIZE, IARG_END); } } } } 

Dalam hal membaca dari alamat, kami memanggil fungsi loadAddr2Reg dan menghapus instruksi asli. Berdasarkan ini, loadAddr2Reg harus mengembalikan nilai yang diperlukan kepada kami.


Dengan catatan, semakin sulit ... argumennya bisa dari berbagai jenis dan juga dapat dikirim dengan cara yang berbeda, jadi Anda harus memanggil fungsi yang berbeda sebelum perintah. Pada platform 32-bit, multiMemAccessStore, dan pada 64 storeReg2Addr akan dipanggil. Dan di sini kita tidak menghapus instruksi dari jalur perakitan. Tidak ada masalah untuk menghapusnya, tetapi dalam beberapa kasus tidak mungkin untuk meniru tindakannya. Program untuk beberapa alasan terkadang menabrak sigfault. Ini tidak penting bagi kami, biarlah itu ditulis untuk dirinya sendiri, hal utama adalah bahwa ada kemungkinan mencegat argumen.


Selanjutnya kita perlu melihat alamat apa yang perlu kita intersep, lihat Memory Map untuk chip stm32f103C8T6 kami:


gambar
Kami tertarik pada alamat dengan SRAM dan PERIPH_BASE, mis. Dari 0x20000000 hingga 0x20000000 + 128 * 1024 dan dari 0x40000000 hingga 0x40030000. Yah, atau lebih tepatnya, tidak cukup, saat kita mengingat instruksi rekaman, kita tidak bisa menghapus. Oleh karena itu, catatan di alamat ini akan jatuh di sigfault. Selain itu, ada kemungkinan yang tidak mungkin bahwa alamat ini akan memiliki data dari program kami, bukan chip ini memiliki yang lain. Karena itu, kita pasti perlu memperbaikinya di suatu tempat. Katakanlah pada semacam array.


Kami membuat array ukuran yang diperlukan, dan kemudian mengganti pointer mereka di alamat basis yang ditentukan.


Dalam program kami, sebagai gantinya di berita utama


 #define SRAM_BASE ((uint32_t)0x20000000) /*!< SRAM base address in the alias region */ #define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */ 

Lakukan


  #define SRAM_BASE ((AddrType)pAddrSRAM) #define PERIPH_BASE ((AddrType)pAddrPERIPH) 

dan di mana pAddrSRAM dan pAddrPERIPH adalah pointer ke array yang dialokasikan sebelumnya.


Sekarang, klien PinTool kami perlu menyampaikan bagaimana kami memperbaiki alamat yang diperlukan.
Hal paling sederhana yang menurut saya bagaimana melakukannya adalah mencegat fungsi yang mengembalikan struktur array dari format ini:


 typedef struct { addr_t start_addr; //      addr_t end_addr; //   addr_t reference_addr; //   } memoryTranslate; 

Sebagai contoh, untuk chip kami akan sangat terisi


 map->start_addr = (addr_t)pAddrSRAM; map->end_addr = 96*1024; map->reference_addr = (addr_t)0x20000000U; 

Tidaklah sulit untuk mencegat fungsi dan mengambil nilai yang diperlukan darinya:


 IMG_AddInstrumentFunction(ImageReplace, 0); .... static memoryTranslate *replaceMemoryMapFun(CONTEXT *context, AFUNPTR orgFuncptr, sizeMemoryTranslate_t *size) { PIN_CallApplicationFunction(context, PIN_ThreadId(), CALLINGSTD_DEFAULT, orgFuncptr, NULL, PIN_PARG(memoryTranslate *), &addrMap, PIN_PARG(sizeMemoryTranslate_t *), size, PIN_PARG_END()); sizeMap = *size; return addrMap; } static VOID ImageReplace(IMG img, VOID *v) { RTN freeRtn = RTN_FindByName(img, NAME_MEMORY_MAP_FUNCTION); if (RTN_Valid(freeRtn)) { PROTO proto_free = PROTO_Allocate(PIN_PARG(memoryTranslate *), CALLINGSTD_DEFAULT, NAME_MEMORY_MAP_FUNCTION, PIN_PARG(sizeMemoryTranslate_t *), PIN_PARG_END()); RTN_ReplaceSignature(freeRtn, AFUNPTR(replaceMemoryMapFun), IARG_PROTOTYPE, proto_free, IARG_CONTEXT, IARG_ORIG_FUNCPTR, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_END); } } 

Dan buat fungsi yang dicegat kami terlihat seperti ini:


 memoryTranslate * getMemoryMap(sizeMemoryTranslate_t * size){ ... return memoryMap; } 

Apa pekerjaan paling sepele yang dilakukan, masih membuat klien untuk OpenOCD, di klien PinTool saya tidak ingin mengimplementasikannya, jadi saya membuat aplikasi terpisah yang berkomunikasi dengan klien PinTool kami melalui nama fifo.


Dengan demikian, skema antarmuka dan komunikasi adalah sebagai berikut:
gambar
Alur kerja yang disederhanakan pada contoh mencegat alamat 0x123:


gambar
Mari kita lihat apa yang terjadi di sini:


klien PinTool diluncurkan, ini menginisialisasi pencegat kami, memulai program
Program dimulai, perlu alamat alamat register pada beberapa larik thread, fungsi getMemoryMap disebut, yang disadap PinTool kami. Sebagai contoh, salah satu register telah beralih ke alamat 0x123, kami akan melacaknya
Klien PinTool menyimpan nilai-nilai alamat yang dipisahkan
Transfer kendali kembali ke program kami
Lebih jauh, di suatu tempat ada rekaman di alamat terlacak kami 0x123. Fungsi StoreReg2Addr melacak hal ini
Dan mengirimkan permintaan tulis ke klien OpenOCD
Klien mengembalikan jawaban, yang diuraikan. Jika semuanya baik-baik saja, maka kontrol program kembali
Lebih jauh, di suatu tempat dalam program, pembacaan terjadi pada alamat yang dilacak 0x123.
loadAddr2Reg melacak ini dan mengirimkan permintaan OpenOCD ke klien.
Klien OpenOCD memprosesnya dan mengembalikan respons
Jika semuanya baik-baik saja, tetapi nilai dari register MK dikembalikan ke program
Program berlanjut.
Itu saja untuk saat ini, kode sumber dan contoh lengkap akan ada di bagian berikut.

Source: https://habr.com/ru/post/id433504/


All Articles