يتضمن PVS-Studio دعمًا لمجموعة أدوات GNU Arm المدمجة

GNU Arm Embedded Toolchain + PVS-Studio

دخلت الأنظمة المدمجة حياتنا لفترة طويلة وثابتة. متطلبات استقرارها وموثوقيتها عالية جدًا ، وتصحيح الخطأ مكلف. لذلك ، من المهم بشكل خاص للمطورين المضمنين استخدام الأدوات المتخصصة بانتظام لضمان جودة شفرة المصدر. ستتحدث هذه المقالة عن ظهور دعم GNU Arm Embedded Toolchain في محلل PVS-Studio وعيوب التعليمات البرمجية الموجودة في مشروع Mbed OS.

مقدمة


محلل PVS-Studio يدعم بالفعل العديد من المجمعات التجارية للأنظمة المدمجة ، على سبيل المثال:


الآن تمت إضافة أداة مطور أخرى لدعمها - GNU Embedded Toolchain.

مجموعة أدوات غنو المدمجة عبارة عن مجموعة من المترجمين من الذراع على أساس مجموعة مترجم غنو. تم إطلاق الإصدار الرسمي الأول في عام 2012 ، ومنذ ذلك الحين تم تطوير المشروع مع دول مجلس التعاون الخليجي.

الغرض الرئيسي من GNU Embedded Toolchain هو إنشاء كود يعمل على المعدن العاري ، أي مباشرة على المعالج بدون طبقة بينية في شكل نظام تشغيل. تحتوي الحزمة على مُجمِّعين لـ C و C ++ ، ومُجمِّع ، ومجموعة من أدوات GNU Binutils ، ومكتبة Newlib . كود المصدر لجميع المكونات مفتوح بالكامل ومرخص بموجب GNU GPL. من الموقع الرسمي يمكنك تنزيل إصدارات لأنظمة Windows و Linux و macOS.

نظام التشغيل Mbed


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

Mbed OS + PVS-Studio


على الرغم من أن الغرض الرئيسي من هذه المقالة هو التحدث عن دعم GNU Embedded Toolchain ، إلا أنه من الصعب كتابة الكثير عنه. علاوة على ذلك ، ربما ينتظر قراء مقالاتنا وصفًا لبعض الأخطاء المثيرة للاهتمام. حسنًا ، دعنا لا نخدع توقعاتهم ونشغل المحلل في مشروع Mbed OS. هذا هو نظام تشغيل مفتوح المصدر تم تطويره بمساعدة Arm.

الموقع الرسمي: https://www.mbed.com/

كود المصدر: https://github.com/ARMmbed/mbed-os

لم يقع الاختيار على Mbed OS عن طريق الصدفة ، إليك كيفية وصف المؤلفين للمشروع:

يعد Arm Mbed OS نظام تشغيل مضمن مفتوح المصدر مصمم خصيصًا لـ "الأشياء" في إنترنت الأشياء. يتضمن جميع الميزات التي تحتاج إليها لتطوير منتج متصل يعتمد على متحكم Arm Cortex-M ، بما في ذلك الأمان والاتصال و RTOS وبرامج التشغيل لأجهزة الاستشعار وأجهزة الإدخال / الإخراج.

هذا مشروع بناء مثالي باستخدام GNU Embedded Toolchain ، خاصة بالنظر إلى مشاركة Arm في تطويره. سأحجز على الفور لم يكن لدي هدف العثور على أكبر عدد ممكن من الأخطاء وعرضها في مشروع معين ، لذلك يتم مراجعة نتائج المراجعة لفترة وجيزة.

أخطاء


أثناء التحقق من رمز Mbed OS ، أنتج محلل PVS-Studio 693 تحذيرًا ، 86 منها ذات أولوية عالية. لن أعتبرهم جميعًا بالتفصيل ، خاصة وأن العديد منهم يتكرر أو لا يهمهم بشكل خاص. على سبيل المثال ، أنتج المحلل الكثير من التحذيرات V547 (التعبير دائمًا صواب / خطأ) يتعلق بنفس أجزاء التعليمات البرمجية. يمكن تكوين المحلل لتقليل عدد الردود الخاطئة وغير المثيرة للاهتمام بشكل كبير ، ولكن لم يتم تعيين هذه المهمة عند كتابة مقال. يمكن لأولئك الذين يرغبون في رؤية مثال لمثل هذا التكوين الموصوف في مقالة " مواصفات محلل PVS-Studio باستخدام مثال مكتبات EFL الأساسية ، 10-15٪ من الإيجابيات الخاطئة ".

بالنسبة للمقالة ، حددت بعض الأخطاء المثيرة للاهتمام لإثبات تشغيل المحلل.

تسرب الذاكرة


لنبدأ مع فئة الأخطاء الشائعة في C و C ++ - تسرب الذاكرة.

تحذير المحلل: V773 CWE-401 تم الخروج من الوظيفة دون تحرير مؤشر 'read_buf'. من الممكن حدوث تسرب للذاكرة. 565

int32_t cfstore_test_init_1(void) { .... read_buf = (char*) malloc(max_len); if(read_buf == NULL) { CFSTORE_ERRLOG(....); return ret; } .... while(node->key_name != NULL) { .... ret = drv->Create(....); if(ret < ARM_DRIVER_OK){ CFSTORE_ERRLOG(....); return ret; // <= } .... free(read_buf); return ret; } 

الوضع الكلاسيكي عند العمل مع الذاكرة الديناميكية. يتم استخدام المخزن المؤقت المخصص لـ malloc فقط داخل الوظيفة ويتم تحريره قبل الخروج. تكمن المشكلة في أن هذا لا يحدث إذا توقفت الوظيفة عن العمل مبكرًا. لاحظ نفس الرمز في الكتل if . على الأرجح ، نسخ المؤلف الجزء العلوي ونسي إضافة مكالمة مجانية .

مثال آخر مشابه للمثال السابق.

تحذير المحلل: V773 CWE-401 تم إنهاء الوظيفة بدون تحرير مؤشر "الواجهة". من الممكن حدوث تسرب للذاكرة. نانوستاكمايكنترفيس. cpp 204

 nsapi_error_t Nanostack::add_ethernet_interface( EMAC &emac, bool default_if, Nanostack::EthernetInterface **interface_out, const uint8_t *mac_addr) { .... Nanostack::EthernetInterface *interface; interface = new (nothrow) Nanostack::EthernetInterface(*single_phy); if (!interface) { return NSAPI_ERROR_NO_MEMORY; } nsapi_error_t err = interface->initialize(); if (err) { return err; // <= } *interface_out = interface; return NSAPI_ERROR_OK; } 

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

Memset


غالبًا ما يؤدي استخدام وظيفة memset إلى حدوث أخطاء ؛ ويمكن العثور على أمثلة للمشكلات المرتبطة بها في مقالة " الوظيفة الأكثر خطورة في عالم C / C ++ ".

خذ بعين الاعتبار تحذير المحلل التالي:

V575 CWE-628 تقوم دالة "memset" بمعالجة عناصر "0". افحص الحجة الثالثة. 282

 mbed_error_status_t mbed_clear_all_errors(void) { .... //Clear the error and context capturing buffer memset(&last_error_ctx, sizeof(mbed_error_ctx), 0); //reset error count to 0 error_count = 0; .... } 

كان المبرمج يهدف إلى إعادة تعيين الذاكرة التي تشغلها بنية last_error_ctx ، لكنه خلط الوسيطتين الثانية والثالثة. نتيجة لذلك ، يتم تعبئة 0 بايت بقيمة sizeof (mbed_error_ctx) .

بالضبط نفس الخطأ موجود مائة سطر أعلاه:

V575 CWE-628 تقوم دالة "memset" بمعالجة عناصر "0". افحص الحجة الثالثة. mbed_error.c 123

جملة "عودة" غير مشروطة في حلقة


تحذير المحلل: V612 CWE-670 "عودة" غير مشروطة داخل حلقة. 2348

 bool thread_nd_service_anycast_address_mapping_from_network_data ( thread_network_data_cache_entry_t *networkDataList, uint16_t *rlocAddress, uint8_t S_id) { ns_list_foreach(thread_network_data_service_cache_entry_t, curService, &networkDataList->service_list) { // Go through all services if (curService->S_id != S_id) { continue; } ns_list_foreach(thread_network_data_service_server_entry_t, curServiceServer, &curService->server_list) { *rlocAddress = curServiceServer->router_id; return true; // <= } } return false; } 

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

أخطاء في الظروف


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

V547 CWE-570 تعبير 'pcb-> state == LISTEN' خطأ دائمًا. 689

 enum tcp_state { CLOSED = 0, LISTEN = 1, .... }; struct tcp_pcb * tcp_listen_with_backlog_and_err(struct tcp_pcb *pcb, u8_t backlog, err_t *err) { .... LWIP_ERROR("tcp_listen: pcb already connected", pcb->state == CLOSED, res = ERR_CLSD; goto done); /* already listening? */ if (pcb->state == LISTEN) { // <= lpcb = (struct tcp_pcb_listen*)pcb; res = ERR_ALREADY; goto done; } .... } 

يعتبر المحلل أن الشرط pcb-> state == LISTEN دائمًا خطأ ، دعنا نرى السبب.

قبل تعليمة if ، يتم استخدام الماكرو LWIP_ERROR ، والذي يمثل ، وفقًا لمنطق العملية ، تأكيدًا . يبدو إعلانه كما يلي:

 #define LWIP_ERROR(message, expression, handler) do { if (!(expression)) { \ LWIP_PLATFORM_ERROR(message); handler;}} while(0) 

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

في هذا المثال ، يتم التحقق من الشرط 'pcb-> state == CLOSED' ، أي أن الانتقال إلى التسمية المنجزة يحدث عندما يكون لحالة pcb-> أي قيمة أخرى. تتحقق العبارة if التي تلي الاستدعاء إلى LWIP_ERROR من حالة pcb-> لـ LISTEN ، ولكن لا يتم استيفاء هذا الشرط أبدًا ، لأن الحالة على هذا السطر يمكن أن تحتوي على قيمة CLOSED فقط.

ضع في اعتبارك تحذيرًا آخر يتعلق بالظروف: V517 CWE-570 استخدام نمط 'if (A) {...} وإلا إذا تم الكشف عن نمط (A) {...}'. هناك احتمال لوجود خطأ منطقي. خطوط التحقق: 62 ، 65. libdhcpv6_server.c 62

 static void libdhcpv6_address_generate(....) { .... if (entry->linkType == DHCPV6_DUID_HARDWARE_EUI64_TYPE) // <= { memcpy(ptr, entry->linkId, 8); *ptr ^= 2; } else if (entry->linkType == DHCPV6_DUID_HARDWARE_EUI64_TYPE)// <= { *ptr++ = entry->linkId[0] ^ 2; *ptr++ = entry->linkId[1]; .... } } 

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

تعبير غير مرغوب فيه


دعونا نلقي نظرة على رمز ممتع.

تحذير المحلل: V607 تعبير بدون مالك '& Discover_response_tlv'. 562

 static int thread_discovery_response_send( thread_discovery_class_t *class, thread_discovery_response_msg_t *msg_buffers) { .... thread_extension_discover_response_tlv_write( &discover_response_tlv, class->version, linkConfiguration->securityPolicy); .... } 

لنلقِ نظرة الآن على إعلان الماكرو thread_extension_discover_response_tlv_write :

 #define thread_extension_discover_response_tlv_write \ ( data, version, extension_bit)\ (data) 

يتم توسيع الماكرو إلى وسيطة البيانات ، أي استدعائه داخل دالة thread_discovery_response_send بعد أن تتحول المعالجة المسبقة إلى تعبير (& Discover_response_tlv) .

انتظر ماذا


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

الخلاصة


تم توسيع قائمة المترجمات المدعومة في PVS-Studio. إذا كان لديك مشروع مخصص للتجميع باستخدام GNU Arm Embedded Toolchain ، أقترح محاولة اختباره باستخدام محللنا. تنزيل العرض التوضيحي هنا . انتبه أيضًا إلى خيار الترخيص المجاني المناسب لبعض المشاريع الصغيرة.



إذا كنت ترغب في مشاركة هذه المقالة مع جمهور يتحدث الإنجليزية ، فيرجى استخدام رابط الترجمة: Yuri Minaev. يدعم PVS-Studio الآن GNU Arm Embedded Toolchain .

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


All Articles