Khung ngăn xếp bị hỏng GDB - Làm thế nào để gỡ lỗi?


113

Tôi có dấu vết ngăn xếp sau đây. Có thể tạo ra bất kỳ điều gì hữu ích từ điều này để gỡ lỗi không?

Program received signal SIGSEGV, Segmentation fault.
0x00000002 in ?? ()
(gdb) bt
#0  0x00000002 in ?? ()
#1  0x00000001 in ?? ()
#2  0xbffff284 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
(gdb) 

Bắt đầu xem mã ở đâu khi chúng ta nhận được một Segmentation faultvà dấu vết ngăn xếp không hữu ích cho lắm?

LƯU Ý: Nếu tôi đăng mã, thì các chuyên gia SO sẽ cho tôi câu trả lời. Tôi muốn lấy hướng dẫn từ SO và tự tìm câu trả lời, vì vậy tôi không đăng mã ở đây. Xin lỗi.


Có thể là chương trình của bạn đã rơi vào cảnh hoang tàn - bạn có thể khôi phục bất cứ thứ gì từ con trỏ ngăn xếp không?
Carl Norum,

1
Một điều khác cần xem xét là con trỏ khung có được đặt chính xác hay không. Bạn đang xây dựng mà không có tối ưu hóa hoặc vượt qua một lá cờ như thế -fno-omit-frame-pointernào? Ngoài ra, đối với tình trạng hỏng bộ nhớ, valgrindcó thể là một công cụ thích hợp hơn, nếu đó là một lựa chọn cho bạn.
FatalError

Câu trả lời:


155

Những địa chỉ không có thật đó (0x00000002 và những thứ tương tự) thực sự là giá trị PC, không phải giá trị SP. Bây giờ, khi bạn nhận được loại SEGV này, với một địa chỉ PC không có thật (rất nhỏ), 99% thời gian là do gọi thông qua một con trỏ hàm không có thật. Lưu ý rằng cuộc gọi ảo trong C ++ được thực hiện thông qua con trỏ hàm, vì vậy bất kỳ vấn đề nào với cuộc gọi ảo đều có thể biểu hiện theo cách tương tự.

Một lệnh gọi gián tiếp chỉ đẩy PC sau cuộc gọi vào ngăn xếp và sau đó đặt PC thành giá trị đích (không có thật trong trường hợp này), vì vậy nếu đây điều đã xảy ra, bạn có thể dễ dàng hoàn tác bằng cách bật PC ra khỏi ngăn xếp theo cách thủ công . Trong mã x86 32-bit, bạn chỉ cần làm:

(gdb) set $pc = *(void **)$esp
(gdb) set $esp = $esp + 4

Với mã x86 64-bit bạn cần

(gdb) set $pc = *(void **)$rsp
(gdb) set $rsp = $rsp + 8

Sau đó, bạn sẽ có thể thực hiện btvà tìm ra mã thực sự ở đâu.

1% thời gian còn lại, lỗi sẽ do ghi đè ngăn xếp, thường là do làm tràn một mảng được lưu trữ trên ngăn xếp. Trong trường hợp này, bạn có thể hiểu rõ hơn về tình huống bằng cách sử dụng một công cụ như valgrind


5
@George: gdb executable corefilesẽ mở ra gdb với tập tin thực thi và cốt lõi, lúc này bạn có thể làm bt(hoặc các lệnh trên tiếp theo bt) ...
Chris Dodd

2
@mk .. ARM không sử dụng ngăn xếp cho các địa chỉ trả về - thay vào đó nó sử dụng đăng ký liên kết. Vì vậy, nó thường không có vấn đề này, hoặc nếu có, nó thường là do một số lỗi ngăn xếp khác.
Chris Dodd

2
Ngay cả trong ARM, tôi nghĩ, tất cả các thanh ghi Mục đích chung và LR đều được lưu trữ trong ngăn xếp trước khi hàm được gọi bắt đầu thực thi. Sau khi hàm kết thúc, giá trị của LR được đưa vào PC và do đó hàm trả về. Vì vậy, nếu ngăn xếp bị hỏng, chúng ta có thể thấy một giá trị sai là PC phải không? Trong trường hợp này có thể điều chỉnh con trỏ ngăn xếp sẽ dẫn đến ngăn xếp phù hợp và giúp gỡ lỗi vấn đề. Bạn nghĩ sao? làm ơn cho tôi biết suy nghĩ của bạn. Cảm ơn bạn.
mk ..

1
Không có thật có nghĩa là gì?
Danny Lo

5
ARM không phải là x86 - con trỏ ngăn xếp của nó được gọi sp, không phải esphoặc rsp, và lệnh gọi của nó lưu trữ địa chỉ trả về trong thanh lrghi, không phải trên ngăn xếp. Vì vậy, đối với ARM, tất cả những gì bạn thực sự cần để hoàn tác cuộc gọi là set $pc = $lr. Nếu $lrkhông hợp lệ, bạn gặp một vấn đề khó khăn hơn nhiều để giải phóng.
Chris Dodd

44

Nếu tình huống khá đơn giản, câu trả lời của Chris Dodd là câu trả lời hay nhất. Có vẻ như nó đã nhảy qua một con trỏ NULL.

Tuy nhiên, có thể chương trình đã tự bắn vào chân, đầu gối, cổ và mắt trước khi gặp sự cố — ghi đè lên ngăn xếp, làm rối con trỏ khung hình và các tệ nạn khác. Nếu vậy, việc làm sáng tỏ hàm băm không có khả năng hiển thị khoai tây và thịt cho bạn.

Giải pháp hiệu quả hơn sẽ là chạy chương trình dưới trình gỡ lỗi và chuyển qua các chức năng cho đến khi chương trình bị treo. Khi một chức năng gặp sự cố được xác định, hãy bắt đầu lại và bước vào chức năng đó và xác định chức năng mà nó gọi là nguyên nhân gây ra sự cố. Lặp lại cho đến khi bạn tìm thấy một dòng mã vi phạm. 75% thời gian, cách khắc phục sau đó sẽ rõ ràng.

Trong 25% tình huống khác, cái gọi là dòng mã vi phạm là một con cá trích đỏ. Nó sẽ phản ứng với các điều kiện (không hợp lệ) đã thiết lập nhiều dòng trước đó — có thể hàng nghìn dòng trước đó. Nếu đúng như vậy, khóa học tốt nhất được chọn phụ thuộc vào nhiều yếu tố: chủ yếu là sự hiểu biết của bạn về mã và kinh nghiệm với nó:

  • Có lẽ việc thiết lập một điểm theo dõi trình gỡ lỗi hoặc chèn các chẩn đoán printftrên các biến quan trọng sẽ dẫn đến A ha!
  • Có thể việc thay đổi các điều kiện thử nghiệm với các đầu vào khác nhau sẽ cung cấp nhiều thông tin chi tiết hơn là gỡ lỗi.
  • Có thể cặp mắt thứ hai sẽ buộc bạn phải kiểm tra các giả định của mình hoặc thu thập bằng chứng bị bỏ sót.
  • Đôi khi, tất cả những gì cần làm là đi ăn tối và suy nghĩ về những bằng chứng thu thập được.

Chúc may mắn!


13
Nếu không có cặp mắt thứ hai thì vịt cao su được chứng minh là lựa chọn thay thế.
Matt

2
Viết ra phần cuối của bộ đệm cũng có thể làm được. Nó có thể không gặp sự cố khi bạn ghi ra phần cuối của bộ đệm, nhưng khi bạn bước ra khỏi chức năng, nó sẽ chết.
phyatt


28

Giả sử rằng con trỏ ngăn xếp hợp lệ ...

Có thể không thể biết chính xác nơi SEGV xảy ra từ backtrace - tôi nghĩ rằng hai khung ngăn xếp đầu tiên đã bị ghi đè hoàn toàn. 0xbffff284 có vẻ như là một địa chỉ hợp lệ, nhưng hai địa chỉ tiếp theo thì không. Để xem xét kỹ hơn ngăn xếp, bạn có thể thử các cách sau:

gdb $ x / 32ga $ rsp

hoặc một biến thể (thay thế số 32 bằng một số khác). Điều đó sẽ in ra một số từ (32) bắt đầu từ con trỏ ngăn xếp có kích thước khổng lồ (g), được định dạng là địa chỉ (a). Nhập 'help x' để biết thêm thông tin về định dạng.

Công cụ mã của bạn với một số 'printf' của lính canh có thể không phải là một ý tưởng tồi, trong trường hợp này.


Cực kỳ hữu ích, cảm ơn bạn - Tôi có một ngăn xếp chỉ quay lại ba khung hình và sau đó nhấn "Backtrace dừng lại: khung trước đó giống với khung này (ngăn xếp bị hỏng?)"; Trước đây tôi đã thực hiện một điều gì đó chính xác như thế này trong mã trong trình xử lý ngoại lệ CPU, nhưng không thể nhớ ngoài info symbolcách thực hiện điều này trong gdb.
leander

22
FWIW trên các thiết bị ARM 32-bit: x/256wa $sp =)
leander

2
@leander Bạn có thể cho tôi biết X / 256wa là gì không? Tôi cần nó cho ARM 64-bit. Nói chung sẽ rất hữu ích nếu bạn có thể giải thích nó là gì.
mk ..

5
Theo câu trả lời, 'x' = kiểm tra vị trí bộ nhớ; nó in ra một số từ 'w' = (trong trường hợp này là 256) và diễn giải chúng thành địa chỉ 'a' =. Có thêm thông tin trong hướng dẫn sử dụng GDB tại sourceware.org/gdb/current/onlineocs/gdb/Memory.html#Memory .
leander

7

Nhìn vào một số thanh ghi khác của bạn để xem liệu một trong số chúng có con trỏ ngăn xếp được lưu trong bộ nhớ cache hay không. Từ đó, bạn có thể lấy một ngăn xếp. Ngoài ra, nếu điều này được nhúng, ngăn xếp thường được xác định tại một địa chỉ rất cụ thể. Sử dụng nó, đôi khi bạn cũng có thể nhận được một ngăn xếp kha khá. Tất cả điều này giả định rằng khi bạn nhảy đến siêu không gian, chương trình của bạn không bị xáo trộn toàn bộ bộ nhớ trong suốt quá trình ...


3

Nếu đó là ghi đè ngăn xếp, các giá trị cũng có thể tương ứng với thứ gì đó có thể nhận ra được từ chương trình.

Ví dụ: tôi vừa thấy mình đang nhìn vào ngăn xếp

(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x000000000000342d in ?? ()
#2  0x0000000000000000 in ?? ()

0x342dlà 13357, hóa ra là một node-id khi tôi nhập nhật ký ứng dụng cho nó. Điều đó ngay lập tức giúp thu hẹp các trang web ứng cử viên nơi có thể xảy ra ghi đè ngăn xếp.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.