PVS-Studio termasuk dukungan untuk GNU Arm Embedded Toolchain

GNU Arm Embedded Toolchain + PVS-Studio

Sistem tertanam telah lama dan dengan tegas memasuki kehidupan kita. Persyaratan untuk stabilitas dan keandalannya sangat tinggi, dan koreksi kesalahan mahal. Karenanya, sangat penting bagi pengembang tertanam untuk secara teratur menggunakan alat khusus untuk memastikan kualitas kode sumber. Artikel ini akan berbicara tentang dukungan dari GNU Arm Embedded Toolchain di penganalisa PVS-Studio dan cacat kode yang ditemukan dalam proyek OS Mbed.

Pendahuluan


Alat analisa PVS-Studio sudah mendukung beberapa kompiler komersial untuk sistem embedded, misalnya:


Sekarang alat pengembang lain telah ditambahkan untuk mendukung - GNU Embedded Toolchain.

GNU Embedded Toolchain adalah kumpulan kompiler dari Arm berdasarkan Koleksi Kompiler GNU. Rilis resmi pertama berlangsung pada 2012, dan sejak itu proyek ini telah berkembang bersama dengan GCC.

Tujuan utama GNU Embedded Toolchain adalah untuk menghasilkan kode yang berjalan pada bare metal, yaitu langsung pada prosesor tanpa interlayer dalam bentuk sistem operasi. Paket termasuk kompiler untuk C dan C ++, assembler, satu set utilitas GNU Binutils, dan perpustakaan Newlib . Kode sumber untuk semua komponen sepenuhnya terbuka dan dilisensikan di bawah GNU GPL. Dari situs resmi Anda dapat mengunduh versi untuk Windows, Linux dan macOS.

OS tidur


Untuk menguji penganalisa, Anda membutuhkan kode sumber sebanyak mungkin. Biasanya tidak ada masalah dengan ini, tetapi ketika kita berhadapan dengan pengembangan tertanam yang ditujukan terutama pada perangkat yang termasuk dalam IoT, menemukan cukup banyak proyek besar bisa sulit. Untungnya, masalah ini diselesaikan oleh sistem operasi khusus, kode sumber yang dalam banyak kasus terbuka. Selanjutnya kita akan berbicara tentang salah satunya.

OS Tidur + PVS-Studio


Meskipun tujuan utama dari artikel ini adalah untuk berbicara tentang dukungan untuk GNU Embedded Toolchain, sulit untuk menulis banyak tentang itu. Selain itu, pembaca artikel kami mungkin menunggu deskripsi beberapa kesalahan menarik. Yah, jangan menipu harapan mereka dan menjalankan analisa pada proyek OS Mbed. Ini adalah sistem operasi open source yang dikembangkan dengan bantuan Arm.

Situs web resmi: https://www.mbed.com/

Kode Sumber: https://github.com/ARMmbed/mbed-os

Pilihan pada Mbed OS tidak jatuh secara tidak sengaja, berikut adalah cara penulis menggambarkan proyek:

Arm Mbed OS adalah sistem operasi tertanam sumber terbuka yang dirancang khusus untuk "hal-hal" di Internet of Things. Ini mencakup semua fitur yang Anda perlukan untuk mengembangkan produk yang terhubung berdasarkan mikrokontroler Arm Cortex-M, termasuk keamanan, konektivitas, RTOS dan driver untuk sensor dan perangkat I / O.

Ini adalah proyek pembangunan ideal menggunakan GNU Embedded Toolchain, terutama mengingat keterlibatan Arm dalam pengembangannya. Saya akan segera membuat reservasi bahwa saya tidak memiliki tujuan untuk menemukan dan menunjukkan sebanyak mungkin kesalahan dalam proyek tertentu, sehingga hasil ulasan tersebut ditinjau secara singkat.

Kesalahan


Selama verifikasi kode OS Mbed, analisa PVS-Studio menghasilkan 693 peringatan, 86 di antaranya dengan prioritas tinggi. Saya tidak akan membahas semuanya secara rinci, terutama karena banyak dari mereka yang diulang atau tidak menarik. Misalnya, penganalisis menghasilkan banyak peringatan V547 (Ekspresi selalu benar / salah) terkait dengan fragmen kode yang sama. Penganalisis dapat dikonfigurasikan untuk secara signifikan mengurangi jumlah tanggapan yang salah dan tidak menarik, tetapi tugas ini tidak disetel saat menulis artikel. Mereka yang ingin dapat melihat contoh konfigurasi seperti yang dijelaskan dalam artikel " spesifikasi analisa PVS-Studio menggunakan contoh EFL Core Libraries, 10-15% dari false positive ".

Untuk artikel ini, saya memilih beberapa kesalahan menarik untuk menunjukkan operasi penganalisa.

Memori bocor


Mari kita mulai dengan kelas umum kesalahan dalam C dan C ++ - kebocoran memori.

Peringatan Analyzer: V773 CWE-401 Fungsi ini keluar tanpa melepaskan pointer 'read_buf'. Kebocoran memori dimungkinkan. cfstore_test.c 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; } 

Situasi klasik saat bekerja dengan memori dinamis. Buffer yang dialokasikan malloc hanya digunakan di dalam fungsi dan dibebaskan sebelum keluar. Masalahnya adalah ini tidak terjadi jika fungsi berhenti bekerja lebih awal. Catat kode yang sama di blok if . Kemungkinan besar, penulis menyalin fragmen atas dan lupa menambahkan panggilan gratis .

Contoh lain mirip dengan yang sebelumnya.

Peringatan Analyzer: V773 CWE-401 Fungsi ini keluar tanpa melepaskan pointer 'antarmuka'. Kebocoran memori dimungkinkan. nanostackemacinterface.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; } 

Pointer ke memori yang dialokasikan dikembalikan melalui parameter output, tetapi hanya jika panggilan inisialisasi berhasil, dan jika terjadi kesalahan, kebocoran terjadi karena variabel antarmuka lokal keluar dari ruang lingkup dan pointer hilang begitu saja. Di sini, seseorang harus menghapus panggilan, atau setidaknya memberikan alamat yang disimpan dalam variabel antarmuka ke luar dalam hal apa pun, sehingga kode panggilan dapat menangani hal ini.

Memset


Menggunakan fungsi memset sering menyebabkan kesalahan; contoh masalah yang terkait dengannya dapat ditemukan di artikel " Fungsi paling berbahaya di dunia C / C ++ ".

Pertimbangkan peringatan penganalisa berikut:

V575 CWE-628 Fungsi 'memset' memproses elemen '0'. Periksa argumen ketiga. mbed_error.c 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; .... } 

Programmer dimaksudkan untuk mengatur ulang memori yang ditempati oleh struktur last_error_ctx , tetapi mencampurkan argumen kedua dan ketiga. Akibatnya, 0 byte diisi dengan nilai sizeof (mbed_error_ctx) .

Persis kesalahan yang sama ada seratus baris di atas:

V575 CWE-628 Fungsi 'memset' memproses elemen '0'. Periksa argumen ketiga. mbed_error.c 123

Pernyataan 'kembali' tanpa syarat dalam satu lingkaran


Peringatan Analyzer: V612 CWE-670 Suatu 'pengembalian' tanpa syarat dalam satu lingkaran. thread_network_data_storage.c 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; } 

Dalam cuplikan ini, ns_list_foreach adalah makro yang diperluas ke pernyataan for . Loop dalam melakukan tidak lebih dari satu iterasi karena panggilan untuk kembali segera setelah baris di mana parameter output dari fungsi diinisialisasi. Mungkin kode ini berfungsi seperti yang dimaksudkan, tetapi menggunakan loop dalam terlihat agak aneh dalam konteks ini. Kemungkinan besar, inisialisasi rlocAddress dan keluar dari fungsi harus dilakukan berdasarkan kondisi, atau Anda dapat menyingkirkan loop dalam.

Kesalahan dalam kondisi


Seperti yang saya katakan di atas, alat analisa menghasilkan sejumlah besar peringatan V547 yang tidak menarik, jadi saya mempelajarinya dengan lancar dan menulis hanya dua kasus untuk artikel tersebut.

V547 CWE-570 Ekspresi 'pcb-> state == DENGARKAN' selalu salah. lwip_tcp.c 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; } .... } 

Penganalisa menganggap bahwa kondisi pcb-> state == LISTEN selalu salah, mari kita lihat mengapa.

Sebelum pernyataan if , makro LWIP_ERROR digunakan , yang, menurut logika operasinya, menyerupai pernyataan tegas . Iklannya terlihat seperti ini:

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

Jika kondisinya salah, makro melaporkan kesalahan dan mengeksekusi kode yang melewati parameter handler , dalam fragmen kode ini ada lompatan tanpa syarat menggunakan goto .

Dalam contoh ini, kondisi 'pcb-> state == CLOSED' dicentang, yaitu transisi ke label yang dilakukan terjadi ketika pcb-> state memiliki nilai lain. Pernyataan if setelah panggilan ke LWIP_ERROR memeriksa status pcb-> untuk LISTEN , tetapi kondisi ini tidak pernah dipenuhi, karena status pada baris ini hanya dapat berisi nilai TUTUP .

Pertimbangkan satu peringatan lagi terkait dengan ketentuan: V517 CWE-570 Penggunaan pola 'if (A) {...} else jika (A) {...}' terdeteksi. Ada kemungkinan kehadiran kesalahan logis. Periksa baris: 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]; .... } } 

Di sini, jika dan lainnya jika memeriksa kondisi yang sama, sebagai akibatnya kode di tempat lain jika tubuh tidak pernah dieksekusi. Kesalahan seperti itu sering terjadi ketika menulis kode menggunakan metode salin-tempel .

Ekspresi tanpa kepemilikan


Mari kita lihat sepotong kode yang menyenangkan.

Peringatan Analyzer: V607 Ekspresi tanpa pemilik '& Discover_response_tlv '. thread_discovery.c 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); .... } 

Sekarang mari kita lihat deklarasi makro thread_extension_discover_response_tlv_write :

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

Makro diperluas ke argumen data, yaitu, panggilannya di dalam fungsi thread_discovery_response_send setelah preprocessing berubah menjadi ekspresi (& temukan_response_tlv ) .

Tunggu apa


Saya tidak punya komentar. Ini mungkin bukan kesalahan, tetapi kode seperti itu selalu menempatkan saya dalam keadaan yang mirip dengan gambar dalam gambar :).

Kesimpulan


Daftar kompiler yang didukung di PVS-Studio telah diperluas. Jika Anda memiliki proyek yang dimaksudkan untuk perakitan menggunakan GNU Arm Embedded Toolchain, saya sarankan mencoba mengujinya menggunakan penganalisis kami. Unduh demo di sini . Juga perhatikan opsi lisensi gratis , yang cocok untuk beberapa proyek kecil.



Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Yuri Minaev. PVS-Studio Sekarang Mendukung GNU Arm Embedded Toolchain .

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


All Articles