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.
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-osPilihan 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;
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;
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) { ....
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) {
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); if (pcb->state == LISTEN) {
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)
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 ) .
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 .