كيفية التوقف عن كتابة البرامج الثابتة للميكروكونترولر وبدء المعيشة


مرحبًا ، اسمي يوجين ، وقد سئمت من كتابة البرامج الثابتة لأجهزة التحكم الدقيقة. كيف حدث هذا وماذا تفعل به ، دعونا نتعرف عليه.


بعد أن تعمل في البرمجة الكبيرة C ++ و Java و Python ، وما إلى ذلك ، لن تشعر بالرجوع إلى المتحكمين الصغريين الحزبيين. إلى أدواتهم الهزيلة والمكتبات. لكن في بعض الأحيان لا يوجد شيء يمكن القيام به ، فإن مهام الوقت الفعلي والاستقلال الذاتي لا تترك خيارًا. ولكن هناك بعض أنواع المهام التي تتحول إلى حل في هذه المنطقة.


على سبيل المثال ، لا يمكن تخيل معدات الاختبار ، وهي دروس أكثر مملة ومملة في البرمجة المدمجة. بشكل عام ، وكذلك أدوات مريحة لهذا الغرض. تكتب ... أنت فلاش ... أنت وميض ... مؤشر LED (في بعض الأحيان يسجل على UART). جميع الأقلام ، بدون أدوات اختبار متخصصة.


ومن المحبط أيضًا أنه لا توجد اختبارات مفيدة لأجهزة التحكم الدقيقة لدينا. كل شيء فقط من خلال البرامج الثابتة ومن خلال المصحح للاختبار.


ودراسة العمل مع الأجهزة والأجهزة الطرفية الجديدة تتطلب الكثير من الجهد والوقت. خطأ واحد ويجب إعادة ترجمة البرنامج وإعادة تشغيله في كل مرة.


في مثل هذه التجارب ، يكون شيء ما مثل REPL أكثر ملاءمة ، بحيث يمكنك ببساطة وبدون ألم القيام بهذه الأشياء ، على الأقل تافهة ، الأشياء:


\


كيفية الوصول إلى هذا ، تم تخصيص هذه السلسلة من المقالات.


وهذه المرة واجهت مشروعًا كان من الضروري فيه اختبار جهاز معقد إلى حد ما ، مع وجود الكثير من أنواع أجهزة الاستشعار والرقائق الأخرى غير المعروفة بالنسبة لي في وقت سابق ، والتي استخدمت العديد من الأجهزة الطرفية من MK ومجموعة من الواجهات المختلفة. كانت المتعة الخاصة هي عدم امتلاك أكواد مصدر البرنامج الثابت للوحة ، لذلك يجب كتابة جميع الاختبارات من نقطة الصفر ، دون استخدام وقت التشغيل من الكود المصدر.


لقد وعد المشروع بتقديم توستماستر جيدًا والمسابقات مثيرة للاهتمام لمدة شهرين أو نحو ذلك (وعلى الأرجح أكثر).


حسنًا ، هنا لن نبكي. يجب على المرء إما أن يغرق في براري C والبرامج الثابتة التي لا نهاية لها مرة أخرى ، أو يرفض أو يأتي بشيء من أجل تسهيل هذا الدرس. في النهاية ، الكسل والفضول هما محرك التقدم.


آخر مرة ، عندما فهمت OpenOCD ، صادفت نقطة مثيرة للاهتمام في الوثائق مثل


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

مثيرة للاهتمام ... ومن الممكن قراءة وكتابة السجلات الطرفية معهم؟ .. اتضح أن ذلك ممكن ، وإلى جانب ذلك ، يمكن تنفيذ هذه الأوامر عن بُعد من خلال خادم TCL ، الذي يبدأ عند بدء تشغيل openOCD.


فيما يلي مثال على مؤشر LED الوامض لـ 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; 

وتسلسل مماثل من أوامر openOCD


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

والآن ، إذا كنت تفكر في الأبدية وتفكر في البرامج الثابتة لـ MK ... فإن الغرض الرئيسي من هذه البرامج هو الكتابة إلى سجلات الرقائق ؛ البرامج الثابتة التي سوف تفعل شيئا فقط والعمل فقط مع جوهر المعالج لا يوجد لديه استخدام عملي!


ملاحظة

على الرغم من بالطبع يمكنك النظر في سرداب (=


سيتذكر الكثيرون الكثير عن العمل مع المقاطعات. لكنها ليست مطلوبة دائمًا ، وفي حالتي ، يمكنك الاستغناء عنها.


وهكذا ، الحياة تتحسن. في مصدر openOCD ، يمكنك حتى العثور على مثال مثير للاهتمام لاستخدام هذه الواجهة.


جيد جدا فارغة على الثعبان.


من الممكن تمامًا تحويل عناوين التسجيل من ملفات الرأس ، وبدء الكتابة بلغة البرمجة النصية للكوش. يمكنك بالفعل إعداد الشمبانيا ، لكن يبدو لي غير كافٍ ، لأنني أريد استخدام مكتبة الأجهزة الطرفية القياسية أو HAL الجديد للعمل مع الأجهزة الطرفية بدلاً من التدوين مع السجلات.


نقل المكتبات إلى بيثون ... في بعض الكابوس ، سنفعل ذلك. لذلك تحتاج إلى استخدام هذه المكتبات في C أو ... C ++. وفي الايجابيات ، يمكنك تجاوز جميع المشغلين تقريبًا ... لفئاتهم.


والعناوين الأساسية في ملفات الرأس ، استبدلها بكائنات من فصولها.


على سبيل المثال ، في ملف stm32f10x.h


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

استبدال مع


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

لكن الألعاب ذات المؤشرات في المكتبة تقطع هذه الفكرة في مهدها ...


فيما يلي مثال لملف 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; …. 

لذلك من الضروري اعتراض العناوين للعناوين بطريقة مختلفة. كيف يمكن القيام بذلك ربما تستحق نظرة على Valgrind ، وليس مقابل لا شيء لديه memchecker. حسنًا ، يجب أن يعرف حقًا كيفية اعتراض العناوين.


في المستقبل ، أقول إنه من الأفضل عدم النظر إلى هناك ... لقد تمكنت تقريبًا من اعتراض المكالمات إلى العناوين. لجميع الحالات تقريبا ما عدا هذا


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

من الممكن اعتراض العنوان ، لكن لم يعد من الممكن اعتراض البيانات المسجلة. فقط اسم السجل الداخلي الذي تكمن فيه هذه القيمة ، ولكن لا يمكن الوصول إليه من memcheck.


في الواقع ، فاجأني فالغريند ، داخل libVEX الوحش القديم ، والذي لم أجد أي معلومات عنه على الإنترنت. من الجيد العثور على القليل من الوثائق في ملفات الرأس.


ثم كانت هناك أدوات DBI أخرى.


فريدا ، Dynamic RIO ، أكثر من ذلك ، وأخيرا حصلت على Pintool.


كان PinTool بعض الوثائق والأمثلة جيدة جدا. على الرغم من أنني ما زلت لا أملك ما يكفي منها ، إلا أنني اضطررت إلى إجراء بعض التجارب. لقد تحولت الأداة إلى أنها قوية للغاية ، إلا أنها تزعج الشفرة المغلقة والتقييد فقط على منصة إنتل (على الرغم من إمكانية التحايل على ذلك في المستقبل)


لذلك ، نحن بحاجة إلى اعتراض الكتابة والقراءة في عناوين محددة. دعونا نرى ما هي التعليمات المسؤولة عن هذا https://godbolt.org/z/nJS9ci .


بالنسبة إلى x64 ، سيكون هذا هو MOV لكلتا العمليتين.


ول x86 سيكون MOV للكتابة و MOVZ للقراءة.


ملاحظة: من الأفضل عدم تمكين التحسين ، وإلا فقد تظهر تعليمات أخرى.


المفسد العنوان
 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); } } } } 

في حالة القراءة من العنوان ، ندعو وظيفة loadAddr2Reg ونحذف التعليمات الأصلية. بناءً على هذا ، يجب أن يعيد loadAddr2Reg القيمة اللازمة لنا.


مع وجود سجل ، يصبح الأمر أكثر صعوبة ... يمكن أن تكون الوسائط من أنواع مختلفة كما يمكن إرسالها بطرق مختلفة ، لذلك يجب عليك استدعاء وظائف مختلفة قبل الأمر. على نظام أساسي 32 بت ، سيتم استدعاء multiMemAccessStore ، وعلى 64 storeReg2Addr. ونحن هنا لا نحذف التعليمات من خط التجميع. لا توجد مشاكل لإزالته ، لكن في بعض الحالات لا يمكن تقليد الإجراء. البرنامج لسبب ما تعطل في بعض الأحيان إلى sigfault. بالنسبة لنا ، هذا ليس بالأمر الحاسم ، فدعه يكتب لنفسه ، والشيء الرئيسي هو أن هناك إمكانية لاعتراض الحجج.


بعد ذلك نحتاج إلى معرفة العناوين التي نحتاج إلى اعتراضها ، انظر إلى خريطة الذاكرة لشريحة stm32f103C8T6:


الصورة
نحن مهتمون بالعناوين التي تحتوي على SRAM و PERIPH_BASE ، أي من 0x20000000 إلى 0x20000000 + 128 * 1024 ومن 0x40000000 إلى 0x40030000. حسنًا ، أو بالأحرى ، ليس تمامًا ، إذ نذكر تعليمات التسجيل ، لم نتمكن من الحذف. لذلك ، سوف يسقط السجل في هذه العناوين في sigfault. بالإضافة إلى ذلك ، هناك احتمال غير محتمل أن تحتوي هذه العناوين على بيانات من برنامجنا ، وليس لدى هذه الشريحة أخرى. لذلك ، نحن بالتأكيد بحاجة إلى إصلاحها في مكان ما. دعنا نقول على نوع من مجموعة.


نقوم بإنشاء صفائف بالحجم المطلوب ، ثم استبدال مؤشراتها في العنوان الأساسي.


في برنامجنا ، في العناوين بدلاً من ذلك


 #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 */ 

افعل


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

وحيث تمثل pAddrSRAM و pAddrPERIPH مؤشرات إلى صفيفات تم تخصيصها مسبقًا.


الآن ، يحتاج عميل PinTool إلى إيصال طريقة إصلاح العناوين اللازمة بطريقة أو بأخرى.
أبسط شيء بدا لي كيف أفعل ذلك هو اعتراض دالة تقوم بإرجاع بنية صفيف من هذا التنسيق:


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

على سبيل المثال ، لشريحة لدينا سوف يتم شغلها


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

ليس من الصعب اعتراض الوظيفة وأخذ القيم المطلوبة منها:


 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); } } 

وجعل وظيفة اعتراض لدينا تبدو مثل هذا:


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

ما هو العمل الأكثر تافهة الذي تم القيام به ، يبقى لجعل العميل إلى OpenOCD ، في عميل PinTool لم أكن أرغب في تنفيذه ، لذلك قمت بتقديم تطبيق منفصل يتصل به عميل PinTool من خلال المسمى fifo.


وبالتالي ، فإن مخطط واجهات والاتصالات على النحو التالي:
الصورة
سير عمل مبسط على سبيل المثال لاعتراض العنوان 0x123:


الصورة
دعنا نلقي نظرة على ما يحدث هنا:


تم إطلاق عميل PinTool ، حيث يقوم بتهيئة اعتراضية لدينا ، ويبدأ البرنامج
يبدأ البرنامج ، فإنه يحتاج إلى معالجة عناوين السجلات في بعض صفيف سلسلة الرسائل ، تسمى وظيفة getMemoryMap ، والتي يعترضها برنامج PinTool. على سبيل المثال ، انقلب أحد السجلات إلى العنوان 0x123 ، وسنتتبعه
يحافظ عميل PinTool على قيم العناوين غير المرتبطة
نقل السيطرة مرة أخرى إلى برنامجنا
علاوة على ذلك ، في مكان ما يوجد تسجيل على عنواننا المتعقب 0x123. وظيفة StoreReg2Addr بتتبع هذا
ويرسل طلب الكتابة إلى عميل OpenOCD
إرجاع العميل الإجابة ، التي يتم تحليلها. إذا كان كل شيء على ما يرام ، ثم يعود التحكم في البرنامج
علاوة على ذلك ، في مكان ما في البرنامج ، تحدث القراءة في العنوان المتعقب 0x123.
loadAddr2Reg يتتبع هذا ويرسل طلب OpenOCD إلى العميل.
يقوم عميل OpenOCD بمعالجته وإرجاع استجابة
إذا كان كل شيء على ما يرام ، ولكن يتم إرجاع القيمة من سجل MK إلى البرنامج
يستمر البرنامج.
كل هذا في الوقت الحالي ، ستكون أكواد المصدر الكاملة وأمثلة في الأجزاء التالية.

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


All Articles