Menavigasi Tantangan Kompilator dalam Aplikasi Keamanan Fungsional
Di seluruh industri, keamanan fungsional mengenakan persyaratan yang ketat pada pengembang: kode harus dirancang secara defensif untuk melindungi dari peristiwa tidak terduga seperti korupsi memori atau interferensi sinar kosmik. Bahasa tingkat tinggi seperti C dan C++ berisi fitur yang perilakunya tidak sepenuhnya didefinisikan oleh spesifikasi bahasa. Perilaku yang tidak terdefinisi ini dapat menghasilkan hasil yang mengejutkan, berpotensi berbahaya dalam sistem yang kritis untuk keselamatan. Akibatnya, standar memandang pengkodean defensif, kemampuan uji yang ketat, cakupan kode yang komprehensif, dan pelacakan dari persyaratan hingga kode aplikasi untuk memastikan implementasi lengkap dan unik dari fungsi keselamatan.
Sektor yang kritis untuk keselamatan, terutama otomotif, sering memerlukan alat diagnostik, kalibrasi, dan pengembangan eksternal yang canggih. Pengkodean defensif dan akses data eksternal bukan konsep yang secara inherent dipahami oleh kompilator. Tidak ada C maupun C++ yang mengakomodasi korupsi memori, jadi jika langkah pertahanan tidak secara eksplisit dapat dijangkau dalam kode, kompilator mungkin mengoptimalkan mereka menghilang. Pertahanan oleh karena itu harus tetap dapat dijangkau secara sintaksis dan semantik untuk bertahan dalam melewati optimasi.
Perilaku yang tidak terdefinisi tidakor dikenal sulit untuk terdeteksi. Bahkan saat teridentifikasi, tidak ada jaminan bahwa biner yang dikompilasi akan berperilaku sesuai yang diinginkan. Akses data “pintu belakang” yang digunakan oleh alat debugging adalah contoh lain dari konstruksi bahasa yang tidak dipertimbangkan oleh kompilator, yang mengakibatkan efek samping yang tidak terduga.
Optimasi kompilator dapat menghapus kode pertahanan yang tampaknya benar jika ia berada pada jalur yang tidak dapat dijalani—yang tidak dapat dijalankan oleh masukan apa pun. Bahkan kode yang lolos cakupan uji unit mungkin dihapus selama build akhir. Jadi, cakupan sumber tingkat tinggi tidak menjamin bahwa berkas eksekusi berisi pemeriksaan keselamatan yang dimaksudkan.
Verifikasi kode objek (OCV) adalah praktik terbaik untuk sistem di mana kegagalan memiliki konsekuensi parah. OCV memastikan bahwa kode objek yang dikompilasi cocok dengan niat pengembang, persyaratan kritis saat kompilator mungkin mengubah kode dengan cara yang melanggar asumsi keselamatan.
Sebelum dan setelah kompilasi
Keamanan fungsional, keamanan, dan standar pengkodean (IEC 61508, ISO 26262, IEC 62304, MISRA C/C++) menekankan kebutuhan untuk menunjukkan seberapa besar kode sumber yang dieksercisa selama pengujian berbasis persyaratan. Sementara cakupan tinggi mengurangi probabilitas kegagalan lapangan, ia bergantung pada asumsi bahwa kompilator dengan setia mereproduksi perilaku tingkat sumber—asumsi yang tidak dapat dijustifikasi untuk aplikasi yang paling kritis.
Aliran kontrol dan data dari kode objek yang dihasilkan jarang mencerminkan kode sumber secara tepat. Pemetaan 1:1 antara kode objek dan assembly memperlihatkan ketidakcocokan. Misalnya, Gambar 1 menunjukkan assembly yang dihasilkan dari cuplikan kode sumber dengan optimasi dinonaktifkan menggunakan kompilator TI. Ketika optimasi diaktifkan, grafik aliran berubah secara dramatis karena kompilator menerapkan transformasi matematika yang mengasumsikan kehilangan perilaku yang tidak terdefinisi.

Gambar 1: Perbandingan sumber vs assembly yang menggambarkan bagaimana optimasi dapat mengubah aliran kontrol.
Kompilator dapat mengoptimalkan panggilan pertahanan jika mereka menentukan bahwa cabang terkait tidak dapat dijangkau. Gambar 2 menunjukkan kompilasi CLANG di mana panggilan ke penangan error dihapus karena kompilator menyimpulkan bahwa variabel status hanya dapat menahan dua nilai. Optimasi ini, sambil efisien, melanggar niat pengkodean pertahanan.

Gambar 2: CLANG menghapus panggilan error pertahanan karena cabang yang tidak dapat dijangkau.
Mengubah konteks sekitar dapat mengubah keputusan optimasi kompilator. Gambar 3 menambahkan fungsi baru yang mengakses variabel status; kompilator sekarang mempertahankan panggilan pertahanan karena konteks tambahan membuat cabang potensial dapat dijalani. Perubahan lebih lanjut, seperti mengembalikan pointer ke variabel status, memaksa kompilator untuk melestarikan nilai eksplisit dalam assembly (Gambar 4).

Gambar 3: Perubahan konteks melestarikan cabang pertahanan.

Gambar 4: Aliasing pointer mencegah optimasi kode pertahanan.
Dalam uji unit, memanipulasi status melalui pointer dapat mencegah kode pertahanan dioptimalkan menghilang, tetapi perilaku ini tidak bertahan dalam build produksi. Oleh karena itu, cakupan uji unit saja dapat menyembunyikan pemeriksaan keselamatan yang hilang.
Verifikasi kode objek
OCV menyediakan jaring pengaman dengan membandingkan sumber dan assembly. Gambar 6 menunjukkan fungsi sederhana yang, saat dieksekusi dengan satu panggilan uji, mencapai cakupan sumber 100 persen. Namun, grafik aliran yang di-assembly (Gambar 8) mengungkapkan jalur yang tidak dieksercise yang terlewat uji unit. Dengan memaksa uji untuk mencakup semua jalur assembler, OCV mengungkap kesenjangan yang diabaikan oleh uji tingkat sumber.

Gambar 6: Kode sumber yang mencapai cakupan 100 persen dalam uji unit.

Gambar 7: Grafik aliran blok dasar dari sumber.

Gambar 8: Grafik aliran yang di-assembly mengungkap jalur yang tidak teruji.
Sementara OCV tidak dapat mencegah kompilator untuk menerapkan aturan optimasi mereka, ia membawa ketidakcocokan antara niat pengembang dan perilaku berkas eksekusi ke permukaan, memungkinkan tindakan korektif sebelum peluncuran produk.
Praktik terbaik di semua domain
Untuk banyak sistem, cakupan uji unit tingkat sumber sudah cukup untuk memenuhi badan sertifikasi. Namun, dalam aplikasi yang paling kritis untuk keselamatan—di seluruh sektor penerbangan, otomotif, medis, dan industri—menambahkan cakupan sumber dengan OCV esensial. OCV menyelaraskan produk yang dikompilasi dengan persyaratan keselamatan yang ketat yang diminta standar, memberikan kepercayaan tertinggi bahwa berkas eksekusi berperilaku sesuai yang diinginkan.