เหตุใด printf () ไม่ดีสำหรับการดีบักระบบฝังตัว


16

printf()ผมคิดว่ามันเป็นสิ่งที่ดีที่จะพยายามที่จะแก้ปัญหาโครงการไมโครคอนโทรลเลอร์โดยใช้

ฉันสามารถเข้าใจได้ว่าคุณไม่มีสถานที่ที่กำหนดไว้ล่วงหน้าและสามารถใช้หมุดที่มีค่าได้ ในเวลาเดียวกันฉันเห็นคนใช้พิน UART TX สำหรับส่งออกไปยังเทอร์มินัล IDE ด้วยDEBUG_PRINT()มาโครแบบกำหนดเอง


12
ใครบอกคุณว่ามันไม่ดี? "โดยปกติจะไม่ดีที่สุด" ไม่เหมือนกับ "เลวร้าย" อย่างไม่มีเงื่อนไข
Spehro Pefhany

6
การพูดคุยทั้งหมดนี้เกี่ยวกับค่าใช้จ่ายที่มีอยู่หากสิ่งที่คุณต้องทำคือข้อความ "ฉันอยู่ที่นี่" เอาท์พุทคุณไม่ต้องการ printf เลยเพียงแค่กิจวัตรประจำวันเพื่อส่งสตริงไปยัง UART ซึ่งรวมถึงรหัสเพื่อเริ่มต้น UART นั้นอาจมีขนาดต่ำกว่า 100 ไบต์ของรหัส การเพิ่มความสามารถในการแสดงผลของค่าฐานสิบหกจะไม่เพิ่มขึ้นทั้งหมด
tcrosley

7
@ChetanBhargava - ไฟล์ส่วนหัว C มักจะไม่เพิ่มรหัสในการปฏิบัติการ พวกเขามีประกาศ; หากรหัสที่เหลือไม่ได้ใช้สิ่งที่ประกาศไว้รหัสสำหรับสิ่งเหล่านั้นจะไม่ได้รับการเชื่อมโยงหากคุณใช้printfแน่นอนรหัสทั้งหมดที่จำเป็นสำหรับการนำไปใช้printfจะเชื่อมโยงกับปฏิบัติการ แต่นั่นเป็นเพราะรหัสที่ใช้ไม่ใช่เพราะส่วนหัว
Pete Becker

2
@ChetanBhargava คุณไม่จำเป็นต้องรวม <stdio.h> ถ้าคุณหมุนรูทีนแบบเรียบง่ายของคุณเองเพื่อเอาท์พุทสตริงอย่างที่ฉันอธิบาย (อักขระเอาท์พุทไปยัง UART จนกว่าคุณจะเห็น '\ 0') '
tcrosley

2
@tcrosley ฉันคิดว่าคำแนะนำนี้อาจสงสัยหากคุณมีคอมไพเลอร์ที่ทันสมัยถ้าคุณใช้ printf ในกรณีที่เรียบง่ายโดยไม่มีสตริงรูปแบบ gcc และอื่น ๆ ส่วนใหญ่จะแทนที่ด้วยการโทรแบบเรียบง่ายที่มีประสิทธิภาพมากกว่าที่คุณอธิบาย
Vality

คำตอบ:


24

ฉันสามารถเกิดข้อเสียขึ้นกับการใช้ printf () โปรดจำไว้ว่า "ระบบฝังตัว" สามารถมีช่วงจากบางสิ่งบางอย่างที่มีหน่วยความจำของโปรแกรมไม่กี่ร้อยไบต์ไปจนถึงระบบขับเคลื่อน QNX RTOS ที่ขับเคลื่อนด้วย rack-mount แบบเต็มชั้นพร้อมหน่วยความจำกิกะไบต์และเทราไบต์ของหน่วยความจำไม่ลบเลือน

  • มันต้องการบางแห่งในการส่งข้อมูล บางทีคุณอาจมีดีบักหรือพอร์ตโปรแกรมบนระบบอยู่แล้วบางทีคุณอาจไม่มี หากคุณไม่ได้ (หรือคนที่คุณไม่ทำงาน) มันไม่มีประโยชน์เลย

  • มันไม่ใช่ฟังก์ชั่นที่มีน้ำหนักเบาในทุกบริบท นี่อาจเป็นเรื่องใหญ่หากคุณมีไมโครคอนโทรลเลอร์ที่มีหน่วยความจำเพียงไม่กี่ K เนื่องจากการเชื่อมโยงใน printf อาจกิน 4K ทั้งหมดด้วยตัวเอง หากคุณมีไมโครคอนโทรลเลอร์ขนาด 32K หรือ 256K อาจไม่ใช่ปัญหาเลยถ้าคุณมีระบบสมองกลฝังตัวขนาดใหญ่

  • มันไม่มีประโยชน์สำหรับการค้นหาปัญหาบางอย่างที่เกี่ยวข้องกับการจัดสรรหน่วยความจำหรือการขัดจังหวะและสามารถเปลี่ยนพฤติกรรมของโปรแกรมเมื่อมีการรวมคำสั่งหรือไม่

  • มันค่อนข้างไร้ประโยชน์ในการจัดการกับสิ่งที่ไวต่อเวลา คุณควรจะดีกว่าด้วยเครื่องวิเคราะห์ตรรกะและเครื่องวิเคราะห์กระแสไฟฟ้าหรือเครื่องวิเคราะห์โปรโตคอลหรือแม้แต่เครื่องจำลอง

  • หากคุณมีโปรแกรมขนาดใหญ่และคุณต้องรวบรวมใหม่หลายครั้งเมื่อคุณเปลี่ยนคำสั่ง printf และแก้ไขพวกเขาคุณอาจเสียเวลามาก

สิ่งที่ดีคือมันเป็นวิธีที่รวดเร็วในการส่งออกข้อมูลในรูปแบบที่ฟอร์แมตแล้วซึ่งโปรแกรมเมอร์ C ทุกคนรู้วิธีใช้เส้นโค้งการเรียนรู้เป็นศูนย์ หากคุณจำเป็นต้องแยกเมทริกซ์สำหรับตัวกรองคาลมานที่คุณกำลังดีบั๊กมันอาจดีที่จะคายมันออกมาในรูปแบบที่ MATLAB สามารถอ่านได้จริง ๆ แล้วดีกว่าดูที่ตำแหน่ง RAM ทีละตัวในดีบักเกอร์หรืออีมูเลเตอร์ .

ฉันไม่คิดว่ามันเป็นลูกศรที่ไร้ประโยชน์ในตัวสั่น แต่ควรใช้เท่าที่จำเป็นพร้อมกับ gdb หรือ debuggers, อีมูเลเตอร์, อีมูเลเตอร์, นักวิเคราะห์เชิงตรรกะ, ออสซิลโลสโคป, เครื่องมือวิเคราะห์รหัสคงที่, เครื่องมือครอบคลุมโค้ดและอื่น ๆ


3
printf()การนำไปใช้ส่วนใหญ่จะไม่ปลอดภัยต่อเธรด (เช่นไม่ใช่ผู้เข้าร่วมใหม่) ซึ่งไม่ใช่นักฆ่าดีล แต่เป็นสิ่งที่ควรคำนึงถึงเมื่อใช้งานในสภาพแวดล้อมแบบมัลติเธรด
JRobert

1
@JRobert นำเสนอจุดที่ดี .. และแม้ในสภาพแวดล้อมที่ไม่มีระบบปฏิบัติการมันเป็นการยากที่จะทำการดีบักโดยตรงของ ISR แน่นอนถ้าคุณกำลังทำ printf () หรือเลขทศนิยมใน ISR แนวทางอาจจะไม่ดี
Spehro Pefhany

@JRobert เครื่องมือการดีบักใดที่นักพัฒนาซอฟต์แวร์ทำงานในสภาพแวดล้อมแบบมัลติเธรด (ในการตั้งค่าฮาร์ดแวร์ที่การใช้ลอจิกวิเคราะห์และออสซิลโลสโคปไม่สามารถใช้งานได้) มีอะไรบ้าง?
Minh Tran

1
ในอดีตที่ผ่านมาฉันได้พิมพ์ safe-threadof () ของฉันเอง; ใช้เท้าเปล่าทำให้ () หรือ putchar () เทียบเท่าเพื่อคายข้อมูลที่รัดกุมมากไปยังสถานี; ข้อมูลไบนารีที่เก็บไว้ในอาร์เรย์ที่ฉันทิ้ง & ตีความหลังจากการทดสอบรัน ใช้พอร์ต I / O เพื่อกะพริบไฟ LED หรือสร้างพัลส์เพื่อวัดเวลาด้วยออสซิลโลสโคป แยกตัวเลขออกเป็น D / A และวัดด้วย VOM ... รายการนี้ยาวเท่าจินตนาการของคุณและผกผันใหญ่เท่ากับงบประมาณของคุณ! :)
JRobert

19

นอกเหนือจากคำตอบที่ดีอื่น ๆ แล้วการกระทำการส่งข้อมูลไปยังพอร์ตที่อัตรา baud แบบอนุกรมสามารถทำได้ช้ามากเมื่อเทียบกับลูปเวลาของคุณและมีผลกระทบต่อการทำงานส่วนที่เหลือของโปรแกรมของคุณ กระบวนการ).

ตามที่คนอื่น ๆ บอกคุณแล้วไม่มีอะไร "เลวร้าย" เกี่ยวกับการใช้เทคนิคนี้ แต่มันก็เหมือนกับเทคนิคการดีบักอื่น ๆ ที่มีข้อ จำกัด ตราบใดที่คุณรู้และสามารถจัดการกับข้อ จำกัด เหล่านี้ได้ก็เป็นวิธีที่สะดวกสบายมากที่จะช่วยให้คุณได้รับรหัสที่ถูกต้อง

ระบบฝังตัวมีความทึบแสงบางอย่างที่โดยทั่วไปแล้วทำการดีบักเล็กน้อยของปัญหา


8
+1 สำหรับ "ระบบฝังตัวมีความทึบชัดเจน" แม้ว่าฉันจะกลัวคำแถลงนี้อาจเข้าใจได้โดยผู้ที่มีประสบการณ์ที่ดีในการทำงานกับการฝังตัว แต่ก็ทำให้สรุปสถานการณ์ที่ดีและกระชับ มันใกล้เคียงกับคำจำกัดความของ "ฝัง" จริง ๆ แล้ว
njahnke

5

มีปัญหาหลักสองประการที่คุณจะต้องพยายามใช้printfกับไมโครคอนโทรลเลอร์

ขั้นแรกอาจเป็นเรื่องเจ็บปวดที่จะส่งสัญญาณเอาต์พุตไปยังพอร์ตที่ถูกต้อง ไม่เสมอ. แต่บางแพลตฟอร์มก็ยากกว่าแพลตฟอร์มอื่น ไฟล์การกำหนดค่าบางอย่างสามารถจัดทำเป็นเอกสารได้ไม่ดีและอาจจำเป็นต้องทำการทดลองจำนวนมาก

The second is memory. A full blown printf library can be BIG. Sometimes you don't need all of the format specifiers though and specialized versions can be available. For instance, the stdio.h provided by AVR contains three different printf's of varying sizes and functionality.

เนื่องจากการใช้งานคุณสมบัติทั้งหมดที่กล่าวถึงมีขนาดใหญ่พอสมควรvfprintf()จึงสามารถเลือกรสชาติที่แตกต่างกันสามแบบได้โดยใช้ตัวเลือกลิงเกอร์ ค่าเริ่มต้นvfprintf()ใช้ฟังก์ชั่นที่กล่าวถึงทั้งหมดยกเว้นการแปลงจุดลอยตัว เวอร์ชันย่อเล็กสุดของvfprintf()จะพร้อมใช้งานที่ใช้เฉพาะสิ่งอำนวยความสะดวกการแปลงจำนวนเต็มและสตริงขั้นพื้นฐานเท่านั้น แต่#สามารถระบุตัวเลือกเพิ่มเติมได้โดยใช้แฟล็กการแปลง (แฟล็กเหล่านี้จะถูกวิเคราะห์อย่างถูกต้อง

ฉันมีตัวอย่างที่ไม่มีห้องสมุดและฉันมีหน่วยความจำน้อย ดังนั้นฉันไม่มีทางเลือกนอกจากใช้มาโครแบบกำหนดเอง แต่การใช้งานprintfหรือไม่นั้นเป็นสิ่งที่เหมาะสมกับความต้องการของคุณ


ผู้ลงคะแนนโปรดอธิบายสิ่งที่คำตอบของฉันไม่ถูกต้องเพื่อให้ฉันสามารถหลีกเลี่ยงข้อผิดพลาดในโครงการในอนาคตได้หรือไม่
embedded.kyle

4

To add to what Spehro Pefhany was saying about "timing-sensitive stuff": let's take an example. Let's say you have a gyroscope from which your embedded system is taking 1,000 measurements per second. You want to debug these measurements, so you need to print them out. Problem: printing them out causes the system to be too busy to read 1,000 measurements per second, which causes the gyroscope's buffer to overflow, which causes corrupt data to be read (and printed). And so, by printing the data, you have corrupted the data, making you think there is a bug in reading the data when maybe there actually is not. A so-called heisenbug.


ฮ่า ๆ! "heisenbug" เป็นศัพท์เทคนิคหรือไม่ ฉันเดาว่าเกี่ยวข้องกับการวัดขนาดของอนุภาคและหลักการของไฮเซนเบิร์ก ...
Zeta.Investigator

3

เหตุผลที่ใหญ่กว่าสำหรับการไม่ดีบั๊กกับ printf () ก็คือมันมักจะไม่มีประสิทธิภาพไม่เพียงพอและไม่จำเป็น

ไม่มีประสิทธิภาพ: printf () และเครือญาติใช้แฟลชและ RAM จำนวนมากเมื่อเทียบกับสิ่งที่มีอยู่ในไมโครคอนโทรลเลอร์ขนาดเล็ก การเปลี่ยนสิ่งที่บันทึกไว้จะต้องรวบรวมใหม่และกำหนดเป้าหมายใหม่ซึ่งจะทำให้กระบวนการช้าลง นอกจากนี้ยังใช้ UART ที่คุณอาจใช้เพื่อทำงานที่มีประโยชน์

ไม่เพียงพอ: มีรายละเอียดมากมายที่คุณสามารถแสดงผลผ่านลิงก์อนุกรม หากโปรแกรมหยุดทำงานคุณจะไม่ทราบว่าอยู่ที่ไหนเพียงแค่ผลลัพธ์สุดท้ายที่เสร็จสมบูรณ์

ไม่จำเป็น: ไมโครคอนโทรลเลอร์หลายตัวสามารถดีบั๊กจากระยะไกลได้ JTAG หรือโปรโตคอลที่เป็นกรรมสิทธิ์สามารถใช้เพื่อหยุดโปรเซสเซอร์ชั่วคราวดูที่รีจิสเตอร์และ RAM และแม้แต่เปลี่ยนสถานะของโปรเซสเซอร์ที่รันอยู่โดยไม่ต้องคอมไพล์ใหม่ นี่คือสาเหตุที่นักดีบั๊กโดยทั่วไปเป็นวิธีที่ดีกว่าในการดีบักกว่าข้อความสั่งพิมพ์แม้กระทั่งบนพีซีที่มีพื้นที่และพลังงานมากมาย

น่าเสียดายที่แพลตฟอร์มไมโครคอนโทรลเลอร์ที่พบมากที่สุดสำหรับมือใหม่ Arduino ไม่ได้มีการดีบั๊ก AVR รองรับการดีบักแบบรีโมท แต่โปรโตคอล debugWIRE ของ Atmel นั้นเป็นกรรมสิทธิ์และไม่มีเอกสาร คุณสามารถใช้บอร์ด dev อย่างเป็นทางการเพื่อตรวจแก้จุดบกพร่องด้วย GDB แต่ถ้าคุณมีที่คุณอาจไม่กังวลเกี่ยวกับ Arduino อีกต่อไป


คุณไม่สามารถใช้พอยน์เตอร์ของฟังก์ชั่นเพื่อเล่นกับสิ่งที่ถูกบันทึกไว้และเพิ่มความยืดหยุ่นได้ทั้งหมดใช่ไหม
Scott Seidman

3

printf () ไม่ทำงานด้วยตัวเอง มันเรียกฟังก์ชั่นอื่น ๆ อีกมากมายและถ้าคุณมีพื้นที่สแต็คเล็ก ๆ น้อย ๆ คุณอาจไม่สามารถใช้งานได้เลยเพื่อแก้ปัญหาที่ใกล้เคียงกับขีด จำกัด สแต็กของคุณ สตริงรูปแบบอาจวางในหน่วยความจำแทนการอ้างอิงจากแฟลชทั้งนี้ขึ้นอยู่กับคอมไพเลอร์และไมโครคอนโทรลเลอร์ สิ่งนี้สามารถเพิ่มขึ้นอย่างมากหากคุณใส่รหัสของคุณด้วยคำสั่ง printf นี่เป็นปัญหาใหญ่ในสภาพแวดล้อม Arduino - ผู้เริ่มต้นใช้คำสั่ง printf หลายสิบหรือหลายร้อยก็พบเจอปัญหาสุ่มอย่างฉับพลันเพราะพวกเขาเขียนทับกองของพวกเขาด้วยกองซ้อนของพวกเขา


2
ในขณะที่ฉันขอขอบคุณข้อเสนอแนะที่ downvote ให้ แต่ฉันและคนอื่น ๆ ก็จะมีประโยชน์มากขึ้นถ้าคนที่ไม่เห็นด้วยอธิบายปัญหาด้วยคำตอบนี้ เราทุกคนอยู่ที่นี่เพื่อเรียนรู้และแบ่งปันความรู้พิจารณาการแบ่งปันของคุณ
Adam Davis

3

แม้ว่าเราต้องการคายข้อมูลออกไปยังคอนโซลการบันทึกบางรูปแบบ แต่printfโดยทั่วไปแล้วฟังก์ชั่นนี้ไม่ใช่วิธีที่ดีมากในการทำเช่นนั้นเนื่องจากมันจะต้องตรวจสอบสตริงรูปแบบที่ส่งผ่านแล้ว แม้ว่ารหัสไม่เคยใช้ตัวระบุรูปแบบใด ๆ นอกเหนือ%04Xจากนี้โดยทั่วไปคอนโทรลเลอร์จะต้องรวมรหัสทั้งหมดที่จะต้องแยกวิเคราะห์สตริงรูปแบบโดยพลการ ขึ้นอยู่กับคอนโทรลเลอร์ที่แน่นอนที่ใช้อยู่อาจมีประสิทธิภาพมากกว่าในการใช้โค้ดบางอย่างเช่น:

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); }

สำหรับไมโครคอนโทรลเลอร์ PIC บางตัวlog_hexi32(l)อาจใช้คำสั่ง 9 คำสั่งและอาจใช้เวลา 17 (ถ้าlอยู่ในธนาคารที่สอง) ในขณะที่log_hexi32p(&l)ใช้เวลา 2 log_hexi32pฟังก์ชั่นนั้นสามารถเขียนได้เองเพื่อให้มีความยาวประมาณ 14 คำสั่งดังนั้นมันจะจ่ายเอง .


2

จุดหนึ่งที่ไม่มีคำตอบอื่น ๆ ที่ได้กล่าวถึง: ในไมโครพื้นฐาน (IE มีเพียงลูปหลัก () และอาจเป็นสองเท่าของ ISR ที่ทำงานได้ตลอดเวลาไม่ใช่ระบบปฏิบัติการแบบมัลติเธรด) หากระบบล่ม / หยุด / รับ ฟังก์ชั่นการพิมพ์ของคุณจะไม่เกิดขึ้นไม่เกิดขึ้น

นอกจากนี้ผู้คนยังบอกว่า "อย่าใช้ printf" หรือ "stdio.h ใช้พื้นที่มาก" แต่ไม่ได้ให้ทางเลือกมากนัก - Embedded.kyle พูดถึงทางเลือกที่ง่ายกว่าและนั่นก็เป็นสิ่งที่คุณควรจะเป็น แน่นอนว่าต้องทำบนระบบฝังตัวพื้นฐาน รูทีนพื้นฐานในการพ่นอักขระสองสามตัวออกจาก UART อาจเป็นโค้ดขนาดไม่กี่ไบต์


หาก printf ของคุณไม่เกิดขึ้นคุณจะได้เรียนรู้มากมายว่าโค้ดของคุณมีปัญหาที่ไหน
Scott Seidman

สมมติว่าคุณมีเพียงหนึ่ง printf ที่อาจเกิดขึ้นใช่ แต่อินเทอร์รัปต์สามารถยิงได้หลายร้อยครั้งในเวลาที่เรียกใช้ printf () เพื่อเอาอะไรออกจาก UART
John U
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.