Mengapa printf () buruk untuk debugging embedded system?

16

Saya kira itu hal yang buruk untuk mencoba men-debug menggunakan proyek berbasis mikrokontroler printf().

Saya dapat mengerti bahwa Anda tidak memiliki tempat yang telah ditentukan untuk output, dan bahwa itu dapat mengkonsumsi pin yang berharga. Pada saat yang sama saya telah melihat orang menggunakan pin UART TX untuk keluaran ke terminal IDE dengan DEBUG_PRINT()makro khusus .

tarabyte
sumber
12
Siapa yang memberitahumu itu buruk? "Biasanya bukan yang terbaik" tidak sama dengan "buruk" yang tidak memenuhi syarat.
Spehro Pefhany
6
Semua pembicaraan ini tentang berapa banyak overhead yang ada, jika semua yang perlu Anda lakukan adalah menampilkan pesan "Saya di sini", Anda tidak perlu printf sama sekali, hanya rutin untuk mengirim string ke UART. Itu, ditambah kode untuk menginisialisasi UART mungkin di bawah 100 byte kode. Menambahkan kemampuan untuk menghasilkan beberapa nilai hex tidak akan meningkatkan semuanya terlalu banyak.
tcrosley
7
@ChetanBhargava - File header C biasanya tidak menambahkan kode ke file yang dapat dieksekusi. Mereka mengandung deklarasi; jika sisa kode tidak menggunakan hal-hal yang dideklarasikan, kode untuk hal-hal itu tidak terhubung. Jika Anda menggunakan printf, tentu saja, semua kode yang diperlukan untuk mengimplementasikan printfterhubung dengan dieksekusi. Tapi itu karena kode menggunakannya, bukan karena header.
Pete Becker
2
@ChetanBhargava Anda bahkan tidak perlu memasukkan <stdio.h> jika Anda menjalankan rutinitas sederhana Anda sendiri untuk menghasilkan string seperti yang saya jelaskan (menampilkan karakter ke UART sampai Anda melihat '\ 0') '
tcrosley
2
@ tcrosley Saya pikir saran ini mungkin diperdebatkan jika Anda memiliki kompiler modern yang baik, jika Anda menggunakan printf dalam case sederhana tanpa format string gcc dan kebanyakan lainnya menggantinya dengan panggilan sederhana yang lebih efisien yang tidak seperti yang Anda gambarkan.
Vality

Jawaban:

24

Saya dapat menemukan beberapa kelemahan menggunakan printf (). Ingatlah bahwa "sistem tertanam" dapat berkisar dari sesuatu dengan beberapa ratus byte memori program hingga sistem QNX RTOS yang digerakkan rak-mount penuh dengan gigabytes RAM dan terabyte memori nonvolatile.

  • Ini membutuhkan tempat untuk mengirim data. Mungkin Anda sudah memiliki port debug atau pemrograman pada sistem, mungkin Anda tidak. Jika Anda tidak (atau yang Anda miliki tidak berfungsi) itu tidak terlalu berguna.

  • Ini bukan fungsi ringan dalam semua konteks. Ini bisa menjadi masalah besar jika Anda memiliki mikrokontroler dengan hanya beberapa K memori, karena menghubungkan di printf mungkin memakan hingga 4K dengan sendirinya. Jika Anda memiliki mikrokontroler 32K atau 256K, itu mungkin bukan masalah, apalagi jika Anda memiliki sistem tertanam besar.

  • Ini sedikit atau tidak berguna untuk menemukan beberapa jenis masalah terkait dengan alokasi memori atau interupsi, dan dapat mengubah perilaku program ketika pernyataan dimasukkan atau tidak.

  • Ini cukup berguna untuk berurusan dengan hal-hal yang sensitif terhadap waktu. Anda akan lebih baik dengan penganalisis logika dan osiloskop atau penganalisa protokol, atau bahkan simulator.

  • Jika Anda memiliki program besar dan Anda harus mengkompilasi ulang berkali-kali saat Anda mengubah pernyataan printf dan mengubahnya, Anda bisa membuang banyak waktu.

Apa yang baik untuk itu adalah cara cepat untuk menghasilkan data dengan cara yang telah diformat sebelumnya sehingga setiap programmer C tahu cara menggunakan - kurva belajar nol. Jika Anda perlu mengeluarkan matriks untuk filter Kalman yang Anda debug, mungkin lebih baik meludahkannya dalam format yang dapat dibaca MATLAB. Tentunya lebih baik daripada melihat lokasi RAM satu per satu dalam debugger atau emulator .

Saya tidak berpikir itu adalah panah yang tidak berguna dalam quiver, tetapi harus digunakan dengan hemat, bersama dengan gdb atau debugger lainnya, emulator, penganalisa logika, osiloskop, alat analisis kode statis, alat cakupan kode dan sebagainya.

Spehro Pefhany
sumber
3
Sebagian besar printf()implementasi bukan thread-safe (yaitu, non-peserta kembali) yang bukan pembunuh kesepakatan, tetapi sesuatu yang perlu diingat ketika menggunakannya di lingkungan multi-threaded.
JRobert
1
@JRobert memunculkan poin yang bagus .. dan bahkan dalam lingkungan tanpa sistem operasi, sulit untuk melakukan banyak debugging langsung yang berguna dari ISR. Tentu saja jika Anda melakukan printf () atau matematika floating point dalam ISR pendekatannya mungkin tidak aktif.
Spehro Pefhany
@JRobert Alat debugging apa yang digunakan oleh pengembang perangkat lunak dalam lingkungan multi-utas (dalam pengaturan perangkat keras di mana penggunaan Logic Analyzers dan osiloskop tidak praktis) miliki?
Minh Tran
1
Di masa lalu saya telah menggulir printf aman saya sendiri (); barefoot put () atau putchar () yang setara digunakan untuk mengeluarkan data yang sangat ringkas ke terminal; menyimpan data biner dalam larik yang saya buang & tafsirkan setelah uji coba; menggunakan port I / O untuk berkedip LED atau untuk menghasilkan pulsa untuk melakukan pengukuran waktu dengan osiloskop; keluarkan angka ke D / A & diukur dengan VOM ... Daftar ini sepanjang imajinasi Anda dan terbalik sebesar anggaran Anda! :)
JRobert
19

Selain beberapa jawaban yang bagus lainnya, tindakan mengirim data ke port dengan kecepatan baud serial bisa sangat lambat sehubungan dengan waktu putaran Anda, dan berdampak pada cara sisa fungsi program Anda (seperti halnya debug APA PUN proses).

Seperti yang dikatakan orang lain kepada Anda, tidak ada yang "buruk" tentang penggunaan teknik ini, tetapi memang, seperti banyak teknik debug lainnya, memiliki keterbatasan. Selama Anda tahu dan dapat menangani batasan-batasan ini, ini bisa menjadi kemudahan yang sangat nyaman untuk membantu Anda mendapatkan kode Anda dengan benar.

Sistem tertanam memiliki opacity tertentu yang, secara umum, membuat debugging sedikit masalah.

Scott Seidman
sumber
8
+1 untuk "sistem yang disematkan memiliki opacity tertentu". Meskipun saya khawatir pernyataan ini mungkin hanya dapat dipahami oleh mereka yang memiliki pengalaman yang layak bekerja dengan embedded, itu membuat ringkasan situasi yang bagus dan ringkas. Ini dekat dengan definisi "tertanam," sebenarnya.
njahnke
5

Ada dua masalah utama yang akan Anda coba gunakan printfpada mikrokontroler.

Pertama, mungkin merepotkan untuk mengirimkan output ke port yang benar. Tidak selalu. Tetapi beberapa platform lebih sulit daripada yang lain. Beberapa file konfigurasi dapat didokumentasikan dengan buruk dan banyak eksperimen mungkin diperlukan.

Yang kedua adalah memori. printfPerpustakaan yang penuh sesak bisa jadi BESAR. Kadang-kadang Anda tidak memerlukan semua penentu format dan versi khusus dapat tersedia. Misalnya, yang stdio.h disediakan oleh AVR berisi tiga printfukuran dan fungsi yang berbeda-beda.

Karena implementasi penuh dari semua fitur yang disebutkan menjadi cukup besar, tiga rasa yang berbeda vfprintf()dapat dipilih menggunakan opsi tautan. Default vfprintf()menerapkan semua fungsi yang disebutkan kecuali konversi floating point. Versi minimalisasi vfprintf()tersedia yang hanya mengimplementasikan integer yang sangat dasar dan fasilitas konversi string, tetapi hanya #opsi tambahan yang dapat ditentukan menggunakan flag konversi (flag ini diurai dengan benar dari spesifikasi format, tetapi kemudian diabaikan).

Saya memiliki contoh di mana tidak ada perpustakaan tersedia dan saya memiliki memori minimal. Jadi saya tidak punya pilihan selain menggunakan makro kustom. Namun penggunaanprintf atau tidak benar-benar salah satu yang sesuai dengan kebutuhan Anda.

tertanam.kyle
sumber
Bisakah downvoter menjelaskan apa jawaban saya yang salah sehingga saya dapat menghindari kesalahan saya dalam proyek-proyek masa depan?
embedded.kyle
4

Untuk menambah apa yang Spehro Pefhany katakan tentang "hal-hal yang sensitif terhadap waktu": mari kita ambil sebuah contoh. Katakanlah Anda memiliki giroskop tempat sistem tertanam Anda mengambil 1.000 pengukuran per detik. Anda ingin men-debug pengukuran ini, jadi Anda perlu mencetaknya. Masalah: mencetaknya menyebabkan sistem terlalu sibuk untuk membaca 1.000 pengukuran per detik, yang menyebabkan buffer giroskop meluap, yang menyebabkan data rusak dibaca (dan dicetak). Jadi, dengan mencetak data, Anda telah merusak data, membuat Anda berpikir ada bug dalam membaca data ketika mungkin sebenarnya tidak ada. Apa yang disebut heisenbug.

njahnke
sumber
lol! Apakah "heisenbug" benar-benar istilah teknis? Saya kira itu ada hubungannya dengan pengukuran keadaan partikel dan Prinsip
Heisenburg
3

Alasan yang lebih besar untuk tidak melakukan debug dengan printf () adalah bahwa biasanya tidak efisien, tidak memadai, dan tidak perlu.

Tidak efisien: printf () dan kin menggunakan banyak flash dan RAM relatif terhadap apa yang tersedia pada mikrokontroler kecil, tetapi inefisiensi yang lebih besar ada dalam debugging yang sebenarnya. Mengubah apa yang sedang dicatat membutuhkan kompilasi ulang dan pemrograman ulang target, yang memperlambat proses. Itu juga menggunakan UART yang bisa Anda gunakan untuk melakukan pekerjaan yang bermanfaat.

Tidak memadai: Hanya ada begitu banyak detail yang dapat Anda hasilkan melalui tautan serial. Jika program hang, Anda tidak tahu persis di mana, hanya output terakhir yang selesai.

Tidak Perlu: Banyak mikrokontroler dapat di-debug jarak jauh. JTAG atau protokol eksklusif dapat digunakan untuk menjeda prosesor, mengintip register dan RAM, dan bahkan mengubah keadaan prosesor yang berjalan tanpa harus mengkompilasi ulang. Inilah sebabnya mengapa debugger umumnya merupakan cara debug yang lebih baik daripada pernyataan cetak, bahkan pada PC dengan banyak ruang dan daya.

Sangat disayangkan bahwa platform mikrokontroler yang paling umum untuk pemula, Arduino, tidak memiliki debugger. AVR mendukung debugging jarak jauh, tetapi protokol debugWIRE Atmel adalah milik dan tidak berdokumen. Anda dapat menggunakan papan dev resmi untuk melakukan debug dengan GDB, tetapi jika Anda memilikinya Anda mungkin tidak terlalu khawatir tentang Arduino lagi.

Theran
sumber
Tidak bisakah Anda menggunakan fungsi pointer untuk bermain dengan apa yang sedang dicatat, dan menambahkan sejumlah fleksibilitas?
Scott Seidman
3

printf () tidak berfungsi sendiri. Ini memanggil banyak fungsi lain, dan jika Anda memiliki sedikit ruang stack Anda mungkin tidak dapat menggunakannya sama sekali untuk men-debug masalah yang dekat dengan batas stack Anda. Bergantung pada kompiler dan mikrokontroler, format string juga dapat ditempatkan dalam memori, bukan dirujuk dari flash. Ini dapat bertambah secara signifikan jika Anda membumbui kode Anda dengan pernyataan printf. Ini adalah masalah besar di lingkungan Arduino - pemula yang menggunakan lusinan atau ratusan pernyataan printf tiba-tiba mengalami masalah yang tampaknya acak karena mereka menimpa tumpukan mereka dengan tumpukan mereka.

Adam Davis
sumber
2
Meskipun saya menghargai umpan balik yang diberikan downvote itu sendiri, akan lebih bermanfaat bagi saya dan orang lain jika mereka yang tidak setuju menjelaskan masalah dengan jawaban ini. Kami semua di sini untuk belajar dan berbagi pengetahuan, pertimbangkan untuk berbagi pengetahuan Anda.
Adam Davis
3

Bahkan jika seseorang ingin memuntahkan data ke beberapa bentuk konsol logging, printffungsi ini umumnya bukan cara yang sangat baik untuk melakukan itu, karena itu perlu memeriksa string format yang dikirimkan dan menguraikannya saat runtime; bahkan jika kode tidak pernah menggunakan penentu format apa pun selain %04X, pengontrol umumnya harus menyertakan semua kode yang akan diperlukan untuk mengurai string format sewenang-wenang. Bergantung pada pengontrol tepat yang digunakan, mungkin jauh lebih efisien untuk menggunakan kode seperti:

void log_string(const char *st)
{
  int ch;
  do
  {
    ch = *st++;
    if (ch==0) break;
    log_char(ch);
  } while(1);
}
void log_hexdigit(unsigned char d)
{
  d&=15;
  if (d > 9) d+=7;
  log_char(d+'0');
}
void log_hexbyte(unsigned char b)
{ log_hexdigit(b >> 4); log_hexdigit(b); }
void log_hexi16(uint16_t s)
{ log_hexbyte(s >> 8); log_hexbyte(s); }
void log_hexi32(uint32_t i)
{ log_hexbyte(i >> 24); log_hexbyte(i >> 16); log_hexbyte(i >> 8); log_hexbyte(i); }
void log_hexi32p(uint32_t *p) // On a platform where pointers are less than 32 bits
{ log_hexi32(*p); }

Pada beberapa mikrokontroler PIC, log_hexi32(l)kemungkinan akan mengambil 9 instruksi dan mungkin mengambil 17 (jika lada di bank kedua), sementara log_hexi32p(&l)akan mengambil 2. log_hexi32pFungsi itu sendiri dapat ditulis menjadi sekitar 14 instruksi panjang, sehingga akan membayar sendiri jika dipanggil dua kali .

supercat
sumber
2

Satu hal yang tidak ada jawaban lain yang disebutkan: Dalam mikro dasar (IE hanya ada loop utama () dan mungkin beberapa ISR berjalan setiap saat, bukan OS multi-ulir) jika crash / berhenti / mendapat terjebak dalam satu lingkaran, fungsi cetak Anda tidak akan terjadi .

Juga, orang-orang mengatakan "jangan gunakan printf" atau "stdio.h membutuhkan banyak ruang" tetapi tidak memberikan banyak alternatif - embedded.kyle menyebutkan alternatif yang disederhanakan, dan itu adalah hal yang seharusnya Anda lakukan. lakukan sebagai hal yang biasa pada sistem embedded dasar. Rutin dasar untuk menyemprotkan beberapa karakter dari UART dapat berupa beberapa byte kode.

John U
sumber
Jika printf Anda tidak terjadi, Anda telah belajar banyak tentang di mana kode Anda bermasalah.
Scott Seidman
Anggap saja Anda hanya memiliki satu printf yang mungkin terjadi, ya. Tetapi interupsi dapat diaktifkan ratusan kali dalam waktu yang dibutuhkan panggilan printf () untuk mengeluarkan apa pun dari UART
John U