Manufaktur industri
Industri Internet of Things | bahan industri | Pemeliharaan dan Perbaikan Peralatan | Pemrograman industri |
home  MfgRobots >> Manufaktur industri >  >> Industrial Internet of Things >> Tertanam

Compiler di dunia asing keselamatan fungsional

Di seluruh sektor, dunia keamanan fungsional memberikan persyaratan baru pada pengembang. Kode yang aman secara fungsional harus menyertakan kode defensif untuk mempertahankan diri dari kejadian tak terduga yang dapat diakibatkan oleh berbagai penyebab. Misalnya, kerusakan memori karena kesalahan pengkodean atau peristiwa sinar kosmik dapat menyebabkan eksekusi jalur kode yang "tidak mungkin" menurut logika kode. Bahasa tingkat tinggi, terutama C dan C++, menyertakan sejumlah fitur mengejutkan yang perilakunya tidak ditentukan oleh spesifikasi bahasa yang dipatuhi kode. Perilaku tidak terdefinisi ini dapat menyebabkan hasil yang tidak diharapkan dan berpotensi menimbulkan bencana yang tidak dapat diterima dalam aplikasi yang aman secara fungsional. Untuk alasan ini, standar mengharuskan pengkodean defensif diterapkan, kode tersebut dapat diuji, bahwa dimungkinkan untuk menyusun cakupan kode yang memadai, dan bahwa kode aplikasi dapat dilacak ke persyaratan untuk memastikan bahwa sistem mengimplementasikannya secara penuh dan unik.

Kode juga harus mencapai cakupan kode tingkat tinggi, dan di beberapa sektor—khususnya otomotif—biasanya desain memerlukan alat diagnostik, kalibrasi, dan pengembangan eksternal yang canggih. Masalah yang muncul adalah bahwa praktik seperti pengkodean defensif dan akses data eksternal bukanlah bagian dari dunia yang dikenali oleh kompiler. Misalnya, baik C maupun C++ tidak mengizinkan kerusakan memori, jadi kecuali kode yang dirancang untuk melindunginya dapat diakses saat tidak ada kerusakan seperti itu, kode tersebut dapat diabaikan begitu saja saat kode dioptimalkan. Akibatnya, kode defensif harus dapat dijangkau secara sintaksis dan semantik jika tidak ingin "dioptimalkan".

Contoh perilaku yang tidak terdefinisi juga dapat menyebabkan kejutan. Sangat mudah untuk menyarankan bahwa mereka harus dihindari, tetapi seringkali sulit untuk mengidentifikasinya. Jika ada, tidak ada jaminan bahwa perilaku kode yang dapat dieksekusi yang dikompilasi akan sesuai dengan niat pengembang. Akses "pintu belakang" ke data yang digunakan oleh alat debug menunjukkan situasi lain yang tidak diizinkan oleh bahasa tersebut, sehingga dapat menimbulkan konsekuensi yang tidak terduga.

Optimalisasi kompiler dapat berdampak besar pada semua area ini, karena tidak satu pun dari area tersebut yang menjadi tanggung jawab vendor kompiler. Pengoptimalan dapat mengakibatkan hilangnya kode defensif yang tampaknya baik jika dikaitkan dengan "ketidaklayakan"—yaitu, jika kode tersebut ada di jalur yang tidak dapat diuji dan diverifikasi oleh rangkaian nilai input apa pun yang memungkinkan. Bahkan yang lebih mengkhawatirkan, kode defensif yang ditampilkan selama pengujian unit mungkin dihilangkan ketika sistem yang dapat dieksekusi dibangun. Hanya karena cakupan kode pertahanan telah dicapai selama pengujian unit, oleh karena itu tidak menjamin bahwa kode itu ada dalam sistem yang telah selesai.

Di tanah keamanan fungsional yang aneh ini, kompiler mungkin kehabisan elemennya. Itulah sebabnya verifikasi kode objek (OCV) mewakili praktik terbaik untuk sistem apa pun yang memiliki konsekuensi mengerikan terkait dengan kegagalan—dan memang, untuk sistem apa pun yang hanya praktik terbaik yang cukup baik.

Sebelum dan sesudah kompilasi

Praktik verifikasi dan validasi yang diperjuangkan oleh keselamatan fungsional, keamanan, dan standar pengkodean seperti IEC 61508, ISO 26262, IEC 62304, MISRA C, dan C++ sangat menekankan pada menunjukkan seberapa banyak kode sumber aplikasi dijalankan selama pengujian berbasis persyaratan.

Pengalaman telah menunjukkan kepada kita bahwa jika kode telah terbukti bekerja dengan benar maka kemungkinan kegagalan di lapangan jauh lebih rendah. Namun karena fokus dari upaya terpuji ini adalah pada kode sumber tingkat tinggi (tidak peduli apa bahasanya), pendekatan semacam itu menempatkan banyak kepercayaan pada kemampuan kompiler untuk membuat kode objek yang mereproduksi secara tepat apa yang pengembang disengaja. Dalam aplikasi yang paling kritis, asumsi tersirat itu tidak dapat dibenarkan.

Tidak dapat dihindari bahwa kontrol dan aliran data dari kode objek tidak akan menjadi cermin yang tepat dari kode sumber dari mana ia berasal, dan membuktikan bahwa semua jalur kode sumber dapat dilakukan dengan andal tidak membuktikan hal yang sama dari kode objek. . Mengingat bahwa ada hubungan 1:1 antara kode objek dan assembler, perbandingan antara kode sumber dan kode perakitan dapat dikatakan. Perhatikan contoh yang ditunjukkan pada  Gambar 1, di mana kode assembler di sebelah kanan telah dibuat dari kode sumber di sebelah kiri (menggunakan compiler TI dengan pengoptimalan dinonaktifkan).


Gambar 1:Kode assembler di sebelah kanan telah dibuat dari kode sumber di sebelah kiri, menunjukkan perbandingan yang jelas antara kode sumber dan kode assembly. (Sumber:LDRA)

Seperti yang diilustrasikan nanti, ketika kode sumber ini dikompilasi, diagram alur untuk kode assembler yang dihasilkan sangat berbeda dengan sumber karena aturan yang diikuti oleh kompiler C atau C++ mengizinkan mereka untuk memodifikasi kode dengan cara apa pun yang mereka suka, asalkan biner berperilaku “seolah-olah sama”.

Dalam sebagian besar keadaan, prinsip itu sepenuhnya dapat diterima – tetapi ada anomali. Optimalisasi kompiler pada dasarnya adalah transformasi matematis yang diterapkan pada representasi internal kode. Ini mengubah "salah" jika asumsi tidak berlaku – seperti yang sering terjadi di mana basis kode menyertakan contoh perilaku yang tidak terdefinisi, misalnya.

Hanya DO-178C, yang digunakan dalam industri kedirgantaraan, yang menempatkan fokus apa pun pada potensi inkonsistensi berbahaya antara niat pengembang dan perilaku yang dapat dijalankan—dan bahkan kemudian, tidak sulit untuk menemukan pendukung solusi dengan potensi yang jelas untuk membiarkan inkonsistensi tersebut tidak terdeteksi. Namun pendekatan tersebut dimaafkan, faktanya tetap bahwa perbedaan antara kode sumber dan objek dapat memiliki konsekuensi yang menghancurkan dalam aplikasi penting apa pun.

Niat pengembang versus perilaku yang dapat dijalankan

Terlepas dari perbedaan yang jelas antara aliran kode sumber dan objek, mereka bukanlah perhatian utama. Kompiler umumnya adalah aplikasi yang sangat andal, dan meskipun mungkin ada bug seperti pada perangkat lunak lain, implementasi kompiler umumnya akan memenuhi persyaratan desainnya. Masalahnya adalah persyaratan desain tersebut tidak selalu mencerminkan kebutuhan sistem yang aman secara fungsional.

Singkatnya, kompiler dapat diasumsikan secara fungsional sesuai dengan tujuan penciptanya. Tapi itu mungkin tidak sepenuhnya seperti yang diinginkan atau diharapkan, seperti yang diilustrasikan pada Gambar 2 di bawah ini dengan contoh yang dihasilkan dari kompilasi dengan compiler CLANG.


Gambar 2 menunjukkan kompilasi dengan compiler CLANG (Sumber:LDRA)

Jelas bahwa panggilan defensif ke fungsi 'kesalahan' belum diekspresikan dalam kode assembler.

Objek 'status' hanya dimodifikasi ketika diinisialisasi dan dalam kasus 'S0' dan 'S1', sehingga kompiler dapat beralasan bahwa satu-satunya nilai yang diberikan ke 'status' adalah 'S0' dan 'S1.' Kompilator menyimpulkan bahwa 'default' tidak diperlukan karena 'state' tidak akan pernah memiliki nilai lain, dengan asumsi bahwa tidak ada korupsi—dan memang, kompilator membuat asumsi tersebut.

Kompilator juga telah memutuskan bahwa karena nilai objek aktual (13 dan 23) tidak digunakan dalam konteks numerik, ia hanya akan menggunakan nilai 0 dan 1 untuk beralih di antara status dan kemudian menggunakan eksklusif "atau" untuk memperbarui nilai negara. Biner mematuhi kewajiban "seolah-olah" dan kodenya cepat dan ringkas. Dalam kerangka acuannya, kompiler telah melakukan pekerjaan dengan baik.

Perilaku ini memiliki implikasi untuk alat "kalibrasi" yang menggunakan file peta memori penghubung untuk mengakses objek secara tidak langsung, dan untuk akses memori langsung melalui debugger. Sekali lagi, pertimbangan tersebut bukan bagian dari kewenangan kompiler dan oleh karena itu tidak dipertimbangkan selama pengoptimalan dan/atau pembuatan kode.

Sekarang anggaplah kode tersebut tetap tidak berubah, tetapi konteksnya dalam kode yang disajikan kepada kompilator sedikit berubah, seperti pada Gambar 3.


Gambar 3:Kode tetap tidak berubah tetapi konteksnya dalam kode yang disajikan kepada kompilator sedikit berubah. (Sumber:LDRA)

Sekarang ada fungsi tambahan, yang mengembalikan nilai variabel status sebagai bilangan bulat. Kali ini nilai absolut 13 dan 23 penting dalam kode yang dikirimkan ke compiler. Meski begitu, nilai-nilai tersebut tidak dimanipulasi dalam fungsi pembaruan (yang tetap tidak berubah) dan hanya terlihat dalam fungsi "f" baru kami.

Singkatnya, kompilator melanjutkan (dengan tepat) untuk membuat penilaian nilai tentang di mana nilai 13 dan 23 harus digunakan—dan nilai tersebut sama sekali tidak diterapkan dalam semua situasi yang memungkinkan.

Jika fungsi baru diubah untuk mengembalikan pointer ke variabel status kita, kode assembler berubah secara substansial. Karena sekarang ada potensi alias mengakses melalui pointer, compiler tidak bisa lagi menyimpulkan apa yang terjadi dengan objek state. Seperti yang ditunjukkan pada Gambar 4 di bawah ini, tidak dapat disimpulkan bahwa nilai 13 dan 23 tidak penting sehingga nilai tersebut sekarang diekspresikan secara eksplisit di dalam assembler.


Gambar 4:Jika fungsi baru diubah untuk mengembalikan pointer ke variabel status kita, kode assembler berubah secara substansial. Tidak dapat disimpulkan bahwa nilai 13 dan 23 tidak penting sehingga nilai tersebut sekarang dinyatakan secara eksplisit di dalam assembler (Sumber:LDRA).

Implikasi untuk pengujian unit kode sumber

Sekarang perhatikan contoh dalam konteks rangkaian uji unit imajiner. Sebagai konsekuensi dari kebutuhan harness untuk mengakses kode yang sedang diuji, nilai variabel status dimanipulasi dan sebagai konsekuensi default tidak "dioptimalkan". Pendekatan semacam itu sepenuhnya dapat dibenarkan dalam alat uji yang tidak memiliki konteks yang berkaitan dengan sisa kode sumber dan yang diperlukan untuk membuat semuanya dapat diakses, tetapi sebagai efek sampingnya dapat menyamarkan penghilangan kode defensif yang sah oleh kompiler.

Kompilator mengenali bahwa nilai arbitrer ditulis ke variabel status melalui pointer, dan sekali lagi, ia tidak dapat menyimpulkan bahwa nilai 13 dan 23 tidak penting. Akibatnya, mereka sekarang diekspresikan secara eksplisit di dalam assembler. Pada kesempatan ini tidak dapat disimpulkan bahwa S0 dan S1 mewakili satu-satunya nilai yang mungkin untuk variabel keadaan, yang berarti bahwa jalur default mungkin layak. Seperti yang ditunjukkan pada Gambar 5, manipulasi variabel status mencapai tujuannya dan panggilan ke fungsi kesalahan sekarang terlihat di assembler.


Gambar 5:Manipulasi variabel state mencapai tujuannya dan pemanggilan fungsi error sekarang terlihat di assembler. (Sumber:LDRA)

Namun, manipulasi ini tidak akan ada dalam kode yang akan dikirimkan dalam suatu produk, sehingga panggilan ke error() tidak benar-benar ada di sistem yang lengkap.

Pentingnya verifikasi kode objek

Untuk mengilustrasikan bagaimana verifikasi kode objek dapat membantu memecahkan teka-teki ini, pertimbangkan lagi contoh cuplikan kode pertama, yang ditunjukkan pada Gambar 6:


Gambar 6:Ini mengilustrasikan bagaimana verifikasi kode objek dapat membantu menyelesaikan bagaimana panggilan untuk kesalahan tidak ada dalam sistem yang lengkap. (Sumber:LDRA)

Kode C ini dapat ditunjukkan untuk mencapai cakupan kode sumber 100% melalui satu panggilan sebagai berikut:

f_ while4(0,3);

Kode dapat diformat ulang menjadi satu operasi per baris dan direpresentasikan pada diagram alur sebagai kumpulan node "blok dasar", yang masing-masing merupakan urutan kode garis lurus. Hubungan antara blok dasar direpresentasikan pada Gambar 7 menggunakan tepi berarah antara node.


Angka 7:Ini menunjukkan hubungan antara blok dasar menggunakan tepi berarah antara node. (Sumber:LDRA)

Ketika kode dikompilasi, hasilnya seperti yang ditunjukkan di bawah ini (Gambar 8). Elemen biru dari grafik aliran mewakili kode yang belum dieksekusi oleh panggilan f_while4(0,3).

Dengan memanfaatkan hubungan satu-ke-satu antara kode objek dan kode assembler, mekanisme ini memperlihatkan bagian mana dari kode objek yang tidak dieksekusi, mendorong penguji untuk merancang pengujian tambahan dan mencapai cakupan kode assembler yang lengkap—dan karenanya mencapai verifikasi kode objek.


Gambar 8:Ini menunjukkan hasil ketika kode dikompilasi. Elemen biru dari grafik aliran mewakili kode yang belum dieksekusi oleh panggilan f_while4(0,3). (Sumber:LDRA)

Jelas, verifikasi kode objek tidak memiliki kekuatan untuk mencegah kompiler mengikuti aturan desainnya dan secara tidak sengaja menghindari niat terbaik pengembang. Tapi itu bisa, dan memang, membawa ketidakcocokan seperti itu menjadi perhatian orang yang tidak waspada.

Sekarang perhatikan prinsip itu dalam konteks contoh "panggilan untuk kesalahan" sebelumnya. Kode sumber dalam sistem yang lengkap, tentu saja, akan identik dengan yang terbukti pada tingkat pengujian unit dan perbandingannya tidak akan mengungkapkan apa pun. Tetapi penerapan verifikasi kode objek ke sistem yang telah selesai akan sangat berharga dalam memberikan jaminan bahwa perilaku penting dinyatakan seperti yang dimaksudkan oleh pengembang.

Praktik terbaik di dunia mana pun

Jika kompiler menangani kode secara berbeda dalam test harness dibandingkan dengan unit test, apakah cakupan pengujian unit kode sumber bermanfaat? Jawabannya adalah "ya" yang memenuhi syarat. Banyak sistem telah disertifikasi berdasarkan bukti artefak tersebut, dan terbukti aman dan andal dalam layanan. Tetapi untuk sistem yang paling kritis di semua sektor, jika proses pengembangannya adalah untuk bertahan dari pemeriksaan yang paling mendetail dan mematuhi praktik terbaik, maka cakupan pengujian unit tingkat sumber harus dilengkapi dengan OCV. Masuk akal untuk mengasumsikan bahwa itu memenuhi kriteria desainnya, tetapi kriteria tersebut tidak termasuk pertimbangan keselamatan fungsional. Verifikasi kode objek saat ini mewakili pendekatan yang paling terjamin ke dunia keamanan fungsional di mana perilaku penyusun sesuai dengan standar, namun demikian mungkin memiliki dampak negatif yang signifikan.


Tertanam

  1. Pentingnya Keselamatan Listrik
  2. Dunia Pewarna Tekstil
  3. Aplikasi Pewarna Asam dalam Dunia Kain
  4. Sekilas Dunia Pewarna
  5. Banyaknya Penggunaan Keranjang Pengaman
  6. Dunia Simulasi yang Berkembang Cepat
  7. Ibukota Manufaktur Dunia
  8. 5 Tips Keamanan Derek Paling Penting
  9. Pentingnya Bahan Gesekan dalam Sistem Keselamatan
  10. Keselamatan di pabrik:sumber perbaikan berkelanjutan