Debugging Fortran

Flag compiler untuk debug, sesi gdb pada nama Fortran, dan pemeriksaan array bounds.

Daftar isi

Kesalahan pada program Fortran muncul dalam dua bentuk, yaitu kode yang ditolak kompiler saat kompilasi dan perilaku keliru yang baru terlihat saat program berjalan. Topik ini membahas perangkat untuk bentuk kedua: opsi -g dan -O0 yang membuat program dapat ditelusuri, pemeriksaan batas array dengan -fcheck=bounds yang mengubah pembacaan di luar batas menjadi error yang jelas, serta sesi gdb dalam mode batch untuk memeriksa variabel dan menetapkan breakpoint pada nama Fortran. Program contohnya tersedia di direktori topik ini, dan semua sesi dijalankan dari direktori tersebut.

Flag compiler untuk debug#

Kompilasi untuk debug memakai -g agar informasi baris sumber tertanam pada program, dan -O0 agar urutan eksekusi tetap mengikuti tulisan kode. Kompilasi teroptimasi boleh mengurutkan ulang pernyataan, menyisipkan fungsi (inlining), dan menghapus variabel sementara, sehingga debugger dapat menampilkan posisi yang tidak sesuai baris sumbernya. Dengan -O0 setiap variabel tetap menempati memorinya, sehingga perintah print pada gdb menampilkan nilai yang benar-benar tersimpan dan bukan salinan yang sudah dibuang kompiler. Kombinasi -g -O0 adalah titik mulai baku sebelum penelusuran dilakukan.

Sebagian kesalahan indeks tidak perlu menunggu waktu berjalan untuk terdeteksi. Ketika indeks yang ditulis adalah literal, kompiler dapat menilainya saat kompilasi: program yang menulis y(7) pada array berlima elemen menghasilkan peringatan statis berikut. Temuan semacam ini adalah yang paling murah karena menyebut berkas dan barisnya sebelum program pernah dijalankan, sementara kesalahan waktu berjalan baru muncul setelah input tertentu mengalir masuk.

bash
# tulis contoh ke berkas sementara lalu kompilasi
printf 'program cek\n  real :: y(5)\n  y(7) = 1.0\nend program cek\n' > /tmp/contoh-batas.f90
gfortran -fcheck=bounds /tmp/contoh-batas.f90 -o /tmp/contoh-batas
text Keluaran
/tmp/contoh-batas.f90:3:4:

    3 |   y(7) = 1.0
      |    1
Warning: Array reference at (1) is out of bounds (7 > 5) in dimension 1

Kompiler mengetahui batas y dari deklarasinya, sehingga indeks literal di luar batas dilaporkan tanpa perlu menjalankan program. Ketika indeks baru diketahui saat program berjalan, misalnya karena diambil dari argumen prosedur atau dari batas perulangan yang berubah, analisis statis tidak dapat menjamin apa pun. Pemeriksaan untuk indeks semacam itu harus dipindah ke waktu berjalan, dan itulah pekerjaan opsi pada bagian berikut.

Pemeriksaan batas array saat berjalan#

Opsi -fcheck=bounds menyisipkan pemeriksaan indeks pada setiap akses array. Program berikut mengisi x dengan lima nilai, lalu membacanya dalam perulangan sampai indeks n yang bernilai tujuh, sehingga pembacaan ke-6 dan ke-7 berada di luar batas.

fortran batas.f90
program batas
   implicit none
   real :: x(5)
   integer :: i, n
   n = 7
   do i = 1, 5
      x(i) = real(i) * 0.5
   end do
   do i = 1, n
      print '(a, i0, a, f5.1)', "x(", i, ") = ", x(i)
   end do
end program batas
bash
# kompilasi dengan pemeriksaan batas, lalu jalankan
gfortran -g -O0 -fcheck=bounds batas.f90 -o batas
./batas 2>&1 | grep -vE '0x|^[[:space:]]+at '
text Keluaran
x(1) =   0.5
x(2) =   1.0
x(3) =   1.5
x(4) =   2.0
x(5) =   2.5
At line 10 of file batas.f90
Fortran runtime error: Index '6' of dimension 1 of array 'x' above upper bound of 5

Error termination. Backtrace:

Pembacaan x(6) menghentikan program dengan pesan yang menyebut indeksnya, batas atasnya, dan baris sumbernya. Baris At line 10 of file batas.f90 adalah bagian keluaran itu yang paling berguna: ia menunjuk langsung posisi sumber yang memicu error, sehingga pembaca dapat melompat ke sana tanpa menelusuri alamat memori apa pun. Saringan grep pada perintah di atas membuang baris alamat memori pada jejak tumpukan (backtrace) karena alamat itu berbeda pada setiap eksekusi. Tanpa -fcheck=bounds, pembacaan yang sama berjalan tanpa pesan apa pun, dan nilai yang tercetak adalah isi memori yang kebetulan berada di sana, sebagaimana diperlihatkan sesi gdb berikutnya.

Sesi gdb pada nama Fortran#

gdb dalam mode -batch menjalankan rangkaian perintah lalu berhenti, sehingga satu baris perintah menghasilkan sesi yang dapat diulang. Program berikut memuat modul hitung_mod dengan fungsi kuadrat, dan sesi gdb berhenti di fungsi tersebut untuk memeriksa argumennya.

fortran hitung.f90
module hitung_mod
   implicit none
contains
   pure function kuadrat(x) result(hasil)
      real, intent(in) :: x
      real :: hasil
      hasil = x * x
   end function kuadrat
end module hitung_mod

program utama
   use hitung_mod, only : kuadrat
   implicit none
   print '(f6.2)', kuadrat(2.5)
end program utama
bash
# kompilasi untuk debug
gfortran -g -O0 hitung.f90 -o hitung
# berhenti di kuadrat, periksa argumennya, lalu jalankan sampai selesai
gdb -batch -ex "break kuadrat" -ex run -ex "print x" -ex finish ./hitung 2>&1 |
  grep -vE 'Inferior|Thread debug|libthread_db' | sed 's/0x[0-9a-f]*/0xXXX/g'
text Keluaran
Breakpoint 1 at 0xXXX: file hitung.f90, line 7.

Breakpoint 1, hitung_mod::kuadrat (x=2.5) at hitung.f90:7
7	      hasil = x * x
$1 = 2.5
0xXXX in utama () at hitung.f90:14
14	   print '(f6.2)', kuadrat(2.5)
Value returned is $2 = 6.25

Perintah break kuadrat menerima nama fungsi sebagaimana tertulis di sumber, dan gdb menampilkannya dengan kualifikasi modulnya, yaitu hitung_mod::kuadrat. Baris Breakpoint 1, hitung_mod::kuadrat (x=2.5) at hitung.f90:7 patut dibaca sebagai tiga informasi sekaligus: penanda breakpoint yang kena, nama prosedur beserta nilai argumennya saat itu, dan posisi sumber tempat eksekusi berhenti berikut cuplikan barisnya. Perintah print x menampilkan nilai argumen x, sedangkan finish menjalankan fungsi sampai selesai dan melaporkan nilai kembaliannya. Alamat memori pada keluaran diganti 0xXXX oleh sed karena berbeda antareksekusi, sedangkan bagian yang tersisa identik setiap kali dijalankan.

Sesi gdb juga memperlihatkan apa yang terjadi bila pemeriksaan batas tidak diaktifkan. Program batas.f90 dikompilasi ulang tanpa -fcheck=bounds, gdb berhenti pada baris perulangan pembacaan, dan perintah continue menjalankan program sampai selesai. Pemeriksaan n dan x sebelum continue menegaskan bahwa data yang masuk benar, sehingga satu-satunya penjelasan bagi nilai yang keliru setelahnya adalah pembacaan di luar batas itu sendiri.

bash
# kompilasi tanpa pemeriksaan batas
gfortran -g -O0 batas.f90 -o batas-g
# periksa n dan x, lalu lanjutkan hingga program selesai
gdb -batch -ex "break 9" -ex run -ex "print n" -ex "print x" -ex continue ./batas-g 2>&1 |
  grep -vE 'Inferior|Thread debug|libthread_db' | sed 's/0x[0-9a-f]*/0xXXX/g'
text Keluaran
Breakpoint 1 at 0xXXX: file batas.f90, line 9.

Breakpoint 1, batas () at batas.f90:9
9	   do i = 1, n
$1 = 7
$2 = (0.5, 1, 1.5, 2, 2.5)
x(1) =   0.5
x(2) =   1.0
x(3) =   1.5
x(4) =   2.0
x(5) =   2.5
x(6) =   0.0
x(7) =   0.0

Nilai n adalah 7 dan x berisi lima nilai yang benar, tetapi perulangan membaca x(6) dan x(7) tanpa pesan error dan mencetak isi memori yang bukan milik array. Bandingkan dengan sesi -fcheck=bounds sebelumnya yang menghentikan program tepat pada pembacaan pertama yang melanggar: di sana posisi kesalahan dilaporkan otomatis, di sini ia hanya dapat diturunkan dari inspeksi manual. Perilaku semacam ini adalah sumber hasil keliru yang paling sulit dilacak, dan itulah alasan -fcheck=bounds diaktifkan pada bangun pengembangan.

Kesalahan umum#

  • Men-debug program teroptimasi. Pada -O2, gdb dapat menunjuk baris yang tampak melompat dan variabel yang nilainya sudah dihapus dari memori. Telusuri dengan -g -O0, dan ukur performa dengan bangun teroptimasi.
  • Mengandalkan nilai program tanpa pemeriksaan batas. Program yang berjalan tanpa error belum tentu benar, sebagaimana x(6) yang terbaca sebagai 0.0. Aktifkan -fcheck=bounds selama pengembangan.
  • Menulis breakpoint dengan nama internal kompiler. Rangkaian __hitung_mod_MOD_kuadrat adalah nama bentukan gfortran yang termangkus (mangled), sedangkan gdb pada sesi ini menerima kuadrat atau hitung_mod::kuadrat langsung.

Ringkasan#

  • Kompilasi untuk debug memakai -g -O0, sedangkan -fcheck=bounds mengubah akses array di luar batas menjadi runtime error yang menyebut indeks dan barisnya.
  • gdb mode -batch -ex memeriksa variabel dan menetapkan breakpoint pada nama Fortran sebagaimana tertulis, dengan kualifikasi modul seperti hitung_mod::kuadrat.
  • Pembacaan di luar batas tanpa pemeriksaan berjalan diam-diam dan mengembalikan isi memori yang kebetulan ada di sana.

Referensi#