/ 365วันแห่งโปรแกรม

[365 วันแห่งโปรแกรม #day72] Memory Stomp

วันที่เจ็ดสิบสองของ ‪#‎365วันแห่งโปรแกรม วันนี้เราจะคุยกันเรื่อง Memory Stomp


เมื่อหลายวันก่อนผมเห็นโพสต์จากเพจ StackOverflow เกี่ยวกับเรื่อง Memory Stomp แต่ให้ตายสิ ผมไม่เคยเห็นได้ยินคำว่า Memory Stomp นี่เลย เอิ่มมม สงสัยมันจะเป็นอะไรที่ขั้นสูงเกินไป >< แต่พอกดไปดูเท่านั้นก็ถึงกับต้องอึ้ง นี่มัน นี่มันอะไรกันเนี่ยย...

Memory Stomp

Memory Stomp คือเหตุการณ์ที่มีโค้ดส่วนนึงกระทำอะไรบางอย่างกับ memory ตำแหน่งที่ถูกโค้ดส่วนอื่นใช้งานหรือจองเอาไว้ ซึ่งทำให้อาจจะเกิดข้อผิดพลาดในการทำงานของโปรแกรม

สาเหตุ

จริงๆ แล้วมีหลายสาเหตุที่ทำให้เกิด Memory Stomp แต่สาเหตุหลักๆ มี 2 อย่างคือ

  1. มีการ allocate memory แล้วใช้เกินปริมาณที่ allocate ไว้ (Buffer overrun) ซึ่งส่วนที่เกินมาอาจจะถูก object อื่นใช้อยู่

  2. มีการ access memory ในตำแหน่งที่ถูก deallocate ไปแล้ว อันนี้ปัญหาจะเกิดเมื่อ memory ตำแหน่งนั้นถูกจองโดย object อื่นไปแล้ว

ตัวอย่างโค้ดที่มีปัญหา

จากตัวอย่างใน StackOverflow เช่นกัน เขายกตัวอย่างเป็น Buffer overrun ครับ โดยจะมีการจองพื้นที่สำหรับ int ไว้ 10 ช่อง (int[10]) แล้วก็ทำการจอง temp int เพื่อใช้ count ในลูป แต่มาเขาวนลูปตั้งแต่ 0 ถึง 10 (0 <= i <= 10) แล้วพอนำไปรันบน OS ต่างๆ ผลลัพธ์ที่ได้ก็ต่างกัน บาง OS ทำงานแล้วก็จบไป บาง OS กลับกลายเป็น infinity loop

ผมจะลองทำซ้ำปัญหานี้ครับ โดย Environment สำหรับการทดลองครั้งนี้ของผมคือ

  • Windows 10 (RTM Insider version)

  • Visual Studio 2015 RC

  • LLVM

ผมสร้าง project C++ ปกติใน Visual Studio แล้วเซ็ตให้ใช้ LLVM ในการ build แทน Visual C++ แล้วก็เขียนโค้ดดังนี้

ผมใช้คำสั้ง _getch() จาก conio.h เพื่อให้รอรับค่าจาก keyboard ก่อนที่จะจบการทำงานเพราะ เวลา debug บน Visual Studio จะไม่เห็นอะไรเลย ถ้าไม่สั่งหยุดไว้ = ="

หลังจากนั้นก็รันเลยครับ

before: i = 0   after: i = 0, a[0] = 0
before: i = 1   after: i = 1, a[1] = 0
before: i = 2   after: i = 2, a[2] = 0
before: i = 3   after: i = 3, a[3] = 0
before: i = 4   after: i = 4, a[4] = 0
before: i = 5   after: i = 5, a[5] = 0
before: i = 6   after: i = 6, a[6] = 0
before: i = 7   after: i = 7, a[7] = 0
before: i = 8   after: i = 8, a[8] = 0
before: i = 9   after: i = 9, a[9] = 0
before: i = 10  after: i = 10, a[10] = 0

พบว่าโปรแกรมรันผ่านไปด้วยดีครับที่น่าแปลกใจคือเราสามารถอ้างถึง a[10] ซึ่งไม่ได้มีการจองไว้ได้ พอผมไปค้นดูก็พบว่าใน C/C++ Compiler บางตัว ไม่มีการตรวจสอบขนาดของ array ครับ ทำให้เราสามารถอ้างถึงตำแหน่งนอกเหนือจากที่จองไว้ได้ และนี่เองคือสาเหตุของ Memory Stomp

ส่วนในภาษาที่เป็น Managed Code จะมีการตรวจสอบเรื่องนี้อย่างเคร่งครัด และคืนค่า Exception ทันทีเมื่อพยายาม access ตำแหน่งภายนอก Array แทนที่จะบอกบอกว่ามันเป็น Undefined Behavior และให้คอมไพเลอร์จัดการกันเอง หรือไม่ภาระก็ไปตกอยู่ที่นักพัฒนา >< ดังนั้นเป็นเรื่องไม่แปลกครับที่คนที่เกิดมาในยุค Managed Code แบบเราๆ จะไม่รู้จัก Memory Stomp

ข้อสงสัยต่อมาคือพื้นที่หลัง array เมื่อกี้มันว่างใช่ไหม แล้วตัวแปร i ถูกวางไว้ตรงไหน ผมจึงขอวนลูปกลับจาก 9 ไป -1 แทน

เมื่อเมื่นรันดูจะได้

before: i = 9   after: i = 9, a[9] = 0
before: i = 8   after: i = 8, a[8] = 0
before: i = 7   after: i = 7, a[7] = 0
before: i = 6   after: i = 6, a[6] = 0
before: i = 5   after: i = 5, a[5] = 0
before: i = 4   after: i = 4, a[4] = 0
before: i = 3   after: i = 3, a[3] = 0
before: i = 2   after: i = 2, a[2] = 0
before: i = 1   after: i = 1, a[1] = 0
before: i = 0   after: i = 0, a[0] = 0
before: i = -1  after: i = 0, a[0] = 0
before: i = -1  after: i = 0, a[0] = 0
before: i = -1  after: i = 0, a[0] = 0
before: i = -1  after: i = 0, a[0] = 0
before: i = -1  after: i = 0, a[0] = 0
before: i = -1  after: i = 0, a[0] = 0
before: i = -1  after: i = 0, a[0] = 0
before: i = -1  after: i = 0, a[0] = 0
before: i = -1  after: i = 0, a[0] = 0
before: i = -1  after: i = 0, a[0] = 0
....

เกิด infinity loop ครับ เมื่อเราพยายามจะเปลี่ยนค่า a[-1] ให้เป็น 0 ค่าของ i จะกลายเป็น 0 ด้วย แล้วก็กลับไปวนซ้ำเพราะ i ยังอยู่ในเงื่อนไข วนแบบนี้ไปเรื่อยๆ

ข้อสรุปคือใน Clang ของ LLVM นั้นน่าจะวางตัวแปร i ของเราเอาไว้ก่อน array เลยทำให้เมื่อ access ที่ตำแหน่ง -1 มีผลกระทบต่อ i

สรุป

เราโชคดีครับที่เกิดมาในยุคของ Managed Code เลยไม่ต้องไปเจอเรื่องอะไรแบบนั้น 555 เอ้ยไม่ใช่ สรุปว่า Memory Stomp จะเกิดขึ้นเมื่อมีการ access memory ตำแหน่งเดียวกันจาก object หลายตัว ซึ่งทำให้เกิดปัญหาต่างๆ ตามมา และที่ยิ่งไปกว่านั้นคือตรวจสอบได้ยากว่าต้นตอของปัญหาอยู่ตรงไหน ที่สำคัญคือตอนเขียนโค้ดต้องมีสติครับจะได้ลดโอกาสที่จะเกิดปัญหาเหล่านี้

References

What is a “memory stomp”? - StackOverflow

Why does this for loop exit on some platforms and not on others? - StackOverflow

#‎day72 #365วันแห่งโปรแกรม ‪#‎โครงการ365วันแห่ง‬...