ความเสียหายของหน่วยความจำเป็นปัญหาทั่วไปในโปรแกรมขนาดใหญ่ที่เขียนด้วยภาษาแอสเซมบลีหรือไม่?

Jan 21 2021

จุดบกพร่องของหน่วยความจำเป็นปัญหาที่พบบ่อยในโปรแกรมและโครงการ C ขนาดใหญ่ มันเป็นปัญหาใน 4.3BSD ในตอนนั้นและยังคงเป็นปัญหาอยู่ในปัจจุบัน ไม่ว่าโปรแกรมจะเขียนอย่างระมัดระวังเพียงใดหากมีขนาดใหญ่เพียงพอก็มักจะสามารถค้นพบข้อผิดพลาดในการอ่านหรือเขียนอื่น ๆ ที่อยู่นอกขอบเขตในโค้ดได้

แต่มีครั้งหนึ่งที่มีการเขียนโปรแกรมขนาดใหญ่รวมถึงระบบปฏิบัติการในแอสเซมบลีไม่ใช่ C. บั๊กหน่วยความจำเสียหายเป็นปัญหาทั่วไปในโปรแกรมแอสเซมบลีขนาดใหญ่หรือไม่ แล้วมันเปรียบเทียบกับโปรแกรม C ได้อย่างไร?

คำตอบ

53 Jean-FrançoisFabre Jan 21 2021 at 17:23

การเข้ารหัสในแอสเซมบลีนั้นโหดร้าย

ตัวชี้ Rogue

ภาษาแอสเซมบลีพึ่งพาตัวชี้มากขึ้น (ผ่านการลงทะเบียนที่อยู่) ดังนั้นคุณจึงไม่สามารถพึ่งพาคอมไพเลอร์หรือเครื่องมือวิเคราะห์แบบคงที่เพื่อเตือนคุณเกี่ยวกับความเสียหายของหน่วยความจำ / การโอเวอร์รันบัฟเฟอร์เมื่อเทียบกับ C

ตัวอย่างเช่นใน C คอมไพเลอร์ที่ดีอาจออกคำเตือนที่นั่น:

 char x[10];
 x[20] = 'c';

มีจำนวน จำกัด ทันทีที่อาร์เรย์สลายตัวไปยังตัวชี้จะไม่สามารถทำการตรวจสอบดังกล่าวได้ แต่นั่นเป็นการเริ่มต้น

ในการประกอบหากไม่มีเครื่องมือไบนารีรันไทม์หรือรันไทม์ที่เหมาะสมคุณจะไม่สามารถตรวจพบข้อผิดพลาดดังกล่าวได้

Rogue (ที่อยู่ส่วนใหญ่) ลงทะเบียน

อีกปัจจัยที่ทำให้การชุมนุมแย่ลงคือการเก็บรักษาทะเบียนและการประชุมสายประจำไม่ได้มาตรฐาน / รับประกัน

หากมีการเรียกรูทีนและไม่ได้บันทึกรีจิสเตอร์โดยไม่ได้ตั้งใจมันจะส่งกลับไปยังผู้โทรพร้อมกับรีจิสเตอร์ที่แก้ไข (ข้างรีจิสเตอร์ "เกา" ที่ทราบว่าถูกทิ้งเมื่อออกจากระบบ) และผู้โทรไม่คาดหวัง ซึ่งนำไปสู่การอ่าน / เขียนไปยังที่อยู่ที่ไม่ถูกต้อง ตัวอย่างเช่นในรหัส 68k:

    move.b  d0,(a3)+
    bsr  a_routine
    move.b  d0,(a3)+   ; memory corruption, a3 has changed unexpectedly
    ...

a_routine:
    movem.l a0-a2,-(a7)
    ; do stuff
    lea some_table(pc),a3    ; change a3 if some condition is met
    movem.l (a7)+,a0-a2   ; the routine forgot to save a3 !
    rts

การใช้รูทีนที่เขียนโดยบุคคลอื่นซึ่งไม่ได้ใช้หลักการบันทึกการลงทะเบียนเดียวกันอาจทำให้เกิดปัญหาเดียวกันได้ ฉันมักจะบันทึกการลงทะเบียนทั้งหมดก่อนที่จะใช้งานประจำของคนอื่น

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

โหมดที่อยู่ Rogue

ฉันแก้ไขการละเมิดความทรงจำจำนวนมากในเกม Amiga โบราณ การเรียกใช้งานในสภาพแวดล้อมเสมือนจริงโดยเปิดใช้งาน MMU ในบางครั้งทำให้เกิดข้อผิดพลาดในการอ่าน / เขียนในที่อยู่ปลอม เวลาส่วนใหญ่ที่อ่าน / เขียนไม่มีผลเนื่องจากการอ่านส่งคืน 0 และการเขียนไปในป่า แต่ขึ้นอยู่กับการกำหนดค่าหน่วยความจำอาจมีผลกระทบที่น่ารังเกียจ

นอกจากนี้ยังมีกรณีของการระบุข้อผิดพลาด ฉันเห็นสิ่งต่างๆเช่น:

 move.l $40000,a0

แทนที่จะเป็นทันที

 move.l #$40000,a0

ในกรณีนี้การลงทะเบียนที่อยู่มีสิ่งที่อยู่ใน$40000(อาจเป็นถังขยะ) ไม่ใช่ที่$40000อยู่ สิ่งนี้นำไปสู่ความเสียหายของหน่วยความจำที่เสียหายในบางกรณี เกมมักจะจบลงด้วยการกระทำที่ไม่ได้ผลที่อื่นโดยไม่ได้แก้ไขปัญหานี้เพื่อให้เกมทำงานได้อย่างถูกต้องเกือบตลอดเวลา แต่มีหลายครั้งที่เกมต้องได้รับการแก้ไขอย่างเหมาะสมเพื่อฟื้นฟูพฤติกรรมที่เหมาะสม

ใน C การทำให้เข้าใจผิดค่าของตัวชี้จะนำไปสู่คำเตือน

(เรายอมแพ้กับเกมเช่น "Wicked" ที่มีความเสียหายทางกราฟิกมากขึ้นเรื่อย ๆ ยิ่งคุณมีเลเวลสูงขึ้น แต่ก็ขึ้นอยู่กับวิธีที่คุณผ่านระดับและลำดับของพวกเขาด้วย ... )

ขนาดข้อมูลโกง

ในการประกอบไม่มีประเภทใด ๆ ก็หมายความว่าถ้าฉันทำ

move.w #$4000,d0           ; copy only 16 bits
move.l #1,(a0,d0.l)    ; indexed write on d1, long

การd0ลงทะเบียนได้รับข้อมูลเพียงครึ่งเดียวเท่านั้นที่เปลี่ยนแปลง อาจเป็นสิ่งที่ฉันต้องการอาจจะไม่ใช่ก็ได้ จากนั้นถ้าd0มีค่าเป็นศูนย์ใน 32-16 บิตที่สำคัญที่สุดโค้ดจะทำตามที่คาดไว้มิฉะนั้นจะเพิ่มa0และd0(เต็มช่วง) และผลลัพธ์ที่ได้คือ "ในป่า" การแก้ไขคือ:

move.l #1,(a0,d0.w)    ; indexed write on d1, long

แต่ถ้าd0> $7FFFมันทำอะไรผิดพลาดเช่นกันเพราะd0ถือว่าเป็นลบแล้ว (ไม่ใช่ในกรณีที่มีd0.l) จึงd0ต้องมีการขยายหรือกำบัง ...

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

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

move.w  the_offset(a0),d0

แทน

move.l  the_offset(a0),d0

d0ไม่ได้ตรวจสอบและช่วยให้คุณมีข้อมูลที่ไม่ถูกต้องใน ตรวจสอบให้แน่ใจว่าคุณดื่มกาแฟเพียงพอในขณะเขียนโค้ดหรือเพียงแค่เขียนเอกสารแทน ...

การจัดตำแหน่งข้อมูลอันธพาล

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

ภาษาระดับสูงใช้ประเภทและหลีกเลี่ยงข้อผิดพลาดส่วนใหญ่โดยดำเนินการจัดตำแหน่ง / ช่องว่างภายใน (เว้นแต่จะโกหกอีกครั้ง)

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

24 jackbochsler Jan 22 2021 at 05:41

ฉันใช้เวลาส่วนใหญ่ในอาชีพการเขียนแอสเซมเบลอร์เดี่ยวทีมเล็กและทีมขนาดใหญ่ (Cray, SGI, Sun, Oracle) ฉันทำงานกับระบบฝังตัว, OS, VMs และตัวโหลดบูตสแตรป ความเสียหายของหน่วยความจำแทบจะไม่เกิดขึ้นเลยหากเคยเป็นปัญหา เราจ้างคนที่เก่งและคนที่ล้มเหลวได้รับการจัดการให้เป็นงานต่างๆที่เหมาะสมกับทักษะของพวกเขามากกว่า

นอกจากนี้เรายังทดสอบอย่างคลั่งไคล้ - ทั้งในระดับหน่วยและระดับระบบ เรามีการทดสอบอัตโนมัติที่ทำงานอย่างต่อเนื่องทั้งในเครื่องจำลองและฮาร์ดแวร์จริง

ใกล้สิ้นสุดอาชีพของฉันฉันได้สัมภาษณ์กับ บริษัท แห่งหนึ่งและฉันถามว่าพวกเขาทำการทดสอบอัตโนมัติได้อย่างไร คำตอบของพวกเขา "อะไรนะ!?" คือทั้งหมดที่ฉันต้องการฟังฉันจบการสัมภาษณ์

19 RETRAC Jan 21 2021 at 23:10

ข้อผิดพลาดงี่เง่าง่ายๆมีอยู่มากมายในการประกอบไม่ว่าคุณจะระวังแค่ไหนก็ตาม ปรากฎว่าแม้แต่คอมไพเลอร์ที่โง่เขลาสำหรับภาษาระดับสูงที่กำหนดไว้ไม่ดี (เช่น C) ยัง จำกัด ข้อผิดพลาดที่เป็นไปได้จำนวนมากเนื่องจากความหมายหรือไวยากรณ์ไม่ถูกต้อง ความผิดพลาดจากการกดแป้นพิมพ์พิเศษหรือลืมเพียงครั้งเดียวมีแนวโน้มที่จะปฏิเสธที่จะรวบรวมมากกว่าที่จะรวบรวม สร้างโครงสร้างที่คุณสามารถแสดงออกได้อย่างถูกต้องในการประกอบซึ่งไม่สมเหตุสมผลเลยเพราะคุณทำผิดทั้งหมดมีโอกาสน้อยที่จะแปลเป็นสิ่งที่ยอมรับว่าเป็น C ที่ถูกต้องและเนื่องจากคุณทำงานในระดับที่สูงขึ้นคุณ มีแนวโน้มที่จะเหล่มองแล้วไป "ห๊ะ?" และเขียนสัตว์ประหลาดที่คุณเพิ่งเขียนขึ้นมาใหม่

ดังนั้นการพัฒนาการประกอบและการดีบักจึงเป็นเรื่องที่ไม่น่าให้อภัยอย่างแท้จริง แต่ข้อผิดพลาดดังกล่าวส่วนใหญ่จะทำลายสิ่งต่างๆได้ยากและจะปรากฏในการพัฒนาและการดีบัก ฉันจะเป็นอันตรายต่อการคาดเดาที่มีการศึกษาว่าหากนักพัฒนาปฏิบัติตามสถาปัตยกรรมพื้นฐานเดียวกันและแนวทางการพัฒนาที่ดีเหมือนกันผลิตภัณฑ์ขั้นสุดท้ายควรมีประสิทธิภาพพอ ๆ ประเภทของข้อผิดพลาดที่คอมไพลเลอร์ตรวจจับได้ด้วยแนวทางปฏิบัติในการพัฒนาที่ดีและประเภทของข้อผิดพลาดที่คอมไพเลอร์ไม่สามารถจับได้อาจถูกจับหรือไม่ติดอยู่กับแนวปฏิบัติดังกล่าว แม้ว่าจะใช้เวลานานกว่าจะไปถึงระดับเดียวกันได้

14 WalterMitty Jan 23 2021 at 02:48

ฉันเขียนคนเก็บขยะต้นฉบับสำหรับ MDL ซึ่งเป็นภาษาที่เหมือนเสียงกระเพื่อมย้อนกลับไปในปีพ. ศ. 2514-2572 มันค่อนข้างท้าทายสำหรับฉันในตอนนั้น มันถูกเขียนใน MIDAS ซึ่งเป็นแอสเซมเบลอร์สำหรับ PDP-10 ที่รัน ITS

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

ฉันไม่พบข้อบกพร่องใด ๆ ในรหัสนั้นยกเว้นข้อบกพร่องที่พบจากการตรวจสอบโต๊ะ หลังจากที่เราถ่ายทอดสดไม่มีใครโผล่มาเลยระหว่างที่ฉันดู

ฉันธรรมดาไม่ฉลาดเหมือนเมื่อห้าสิบปีก่อน ฉันไม่สามารถทำอะไรแบบนั้นได้ในวันนี้ และระบบในปัจจุบันมีขนาดใหญ่กว่า MDL หลายพันเท่า

7 Raffzahn Jan 22 2021 at 00:00

บั๊กความเสียหายของหน่วยความจำเป็นปัญหาที่พบบ่อยในโปรแกรม C ขนาดใหญ่ [... ] แต่มีช่วงเวลาหนึ่งที่โปรแกรมขนาดใหญ่รวมถึงระบบปฏิบัติการถูกเขียนขึ้นในแอสเซมบลีไม่ใช่ C

คุณทราบหรือไม่ว่ามีภาษาอื่น ๆ ที่ใช้กันทั่วไปในช่วงต้น ๆ อยู่แล้ว? เช่น COBOL, FORTRAN หรือ PL / 1?

บั๊กหน่วยความจำเสียหายเป็นปัญหาทั่วไปในโปรแกรมประกอบขนาดใหญ่หรือไม่?

ขึ้นอยู่กับปัจจัยหลายประการเช่น

  • Assembler ใช้เนื่องจากโปรแกรมแอสเซมเบลอร์ที่แตกต่างกันมีการสนับสนุนการเขียนโปรแกรมในระดับที่แตกต่างกัน
  • โครงสร้างโปรแกรมโดยเฉพาะอย่างยิ่งโปรแกรมขนาดใหญ่เป็นไปตามโครงสร้างที่ตรวจสอบได้
  • modularisation และอินเทอร์เฟซที่ชัดเจน
  • ชนิดของโปรแกรมที่เขียนขึ้นเนื่องจากไม่ใช่ทุกงานที่ต้องใช้ตัวชี้
  • รูปแบบการปฏิบัติที่ดีที่สุด

A good assembler does not only make sure that data is aligned, but as well offers tools to handle complex data types, structures and alike in abstract fashion, reducing the need to 'manually' calculate pointers.

An assembler used for any serious project is as always a macro assembler (*1), thus capable to encasule primitive operations into higher level macro instructions, enabling a more application centric programming while avoiding many pitfalls of pointer handling (*2).

Program types are also quite influential. Applications usually consist of various modules, many of them can be written almost or complete without (or only controlled) pointer usage. Again, usage of tools provided by the assembler is key to less faulty code.

ต่อไปจะเป็นแนวทางปฏิบัติที่ดีที่สุดซึ่งจะไปพร้อมกับหลาย ๆ ข้อก่อนหน้านี้ อย่าเขียนโปรแกรม / โมดูลที่ต้องการการลงทะเบียนฐานหลายตัวซึ่งส่งมอบหน่วยความจำขนาดใหญ่แทนโครงสร้างคำขอเฉพาะและอื่น ๆ ...

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

     LDA   #<Table
     STA   Pointer

ค่อนข้างบางโปรแกรมที่ฉันเคยเห็นค่อนข้างจะไป

     LDA   #0
     STA   Pointer

(หรือแย่กว่านั้นถ้าเป็น 65C02)

     STZ   Pointer

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

ดังนั้นแนวทางปฏิบัติที่ดีที่สุดจึงทำให้เรากลับมาใช้ Assembler และเครื่องมือทั้งหมดที่มีให้อีกครั้ง

อย่าพยายามเล่นAssembler แทน Assembler - ปล่อยให้เขาทำงานแทนคุณ

And then there is the runtime, something that applies to all languages but is often forgotten. Beside things like stack checking or bounds check on parameters, one of the most effective ways to catch pointer errors is simply locking the first alnd last memory page against write and read (*3). It not only catches the all beloved null pointer error, but as well all low positive or negative numbers which are often result of some prior indexing going wrong. Sure, Runtime is always the last resort, but this one is an easy one.

Above all, maybe the most relevant reason is

  • the machine's ISA

in reducing chances of memory corruption by reducing the need to handle with pointers at all.

โครงสร้าง CPU บางตัวต้องการการดำเนินการของตัวชี้ (โดยตรง) น้อยกว่าโครงสร้างอื่น ๆ มีช่องว่างขนาดใหญ่ระหว่างสถาปัตยกรรมที่รวมหน่วยความจำไปจนถึงการทำงานของหน่วยความจำเทียบกับผู้ที่ไม่ชอบเช่นสถาปัตยกรรมการโหลด / การจัดเก็บตามตัวสะสม จำเป็นต้องมีการจัดการตัวชี้โดยธรรมชาติสำหรับสิ่งที่มีขนาดใหญ่กว่าองค์ประกอบเดียว (ไบต์ / คำ)

ตัวอย่างเช่นในการถ่ายโอนเขตข้อมูลสมมติว่าชื่อลูกค้าจากรอบ ๆ ในหน่วยความจำ a / 360 ใช้การดำเนินการ MVC เดียวกับที่อยู่และความยาวการถ่ายโอนที่สร้างโดยแอสเซมเบลอร์จากนิยามข้อมูลในขณะที่สถาปัตยกรรมโหลด / จัดเก็บที่ออกแบบมาเพื่อจัดการกับแต่ละไบต์ แยกกันต้องตั้งค่าตัวชี้และความยาวในรีจิสเตอร์และวนรอบองค์ประกอบเดียวที่เคลื่อนไหว

เนื่องจากการดำเนินการดังกล่าวเป็นเรื่องปกติจึงอาจเกิดข้อผิดพลาดได้เช่นกัน หรือโดยทั่วไปแล้วอาจกล่าวได้ว่า:

Programms for CISC processors are usually less prone to errors than such written for RISC machines.

Of course and as usual, everything can be screwed up by bad programming.

And how did it compare to C programs?

Much the same - or better, C is the HLL equivalent of the most primitive CPU ISA, so anything offering higher level instructions will fair better.

C is inherently a RISCy language. Operations provided are reduced to a minimum, which goes with a minimum ability for check against unintended operations. Using unchecked pointers is not only standard but required for many operations, opening many possibilities for memory corruption.

Take in contrast a HLL like ADA, here it's almost impossible to create pointer havoc - unless it's intended and explicit declared as option. A good part of it is (like with the ISA before) due higher data types and handling thereof in a typesafe manner.


For the experience part, I did most of my professional life (>30y) in Assembly projects, with like 80% Mainframe (/370) 20% Micros (mostly 8080/x86) - plus private a lot more :) Mainframe programming covered projects as large as 2+ millions LOC (instructions only) while micro projects kept around 10-20k LOC.


*1 - No, something offering away replacing text passages with premade text is at best some textual preprocessor, but not a macro assembler. A macro assembler is a meta tool to create the language needed for a project. It offers tools to tap the information the assembler gathers about the source (field size, field type, and many more) as well as control structures to formulate handling, used to generate appropriate code.

*2 - It's easy to bemoan that C wasn't fit with any serious macro capabilities, it would not only removed the need for many obscure constructs, but as well enabled much advancement by extending the language without the need to write a new one.

*3 - Personally I prefer to make page 0 only write protected and fill the first256 bytes with binary zero. That way all null (or low) pointer writes still result in a machine error, but reading from a null pointer returns, depending on the type, a byte/halfword/word/doublewort containing zero - well, or a null-string :) I know, it's lazy, but it makes life much more if easy one has to incooperate other peoples code. Also the remaining page can be used for handy constant values like pointers to various global sources, ID strings, constant field content and translate tables.

6 waltinator Jan 22 2021 at 09:17

I have written OS mods in assembly on CDC G-21, Univac 1108, DECSystem-10, DECSystem-20, all 36 bit systems, plus 2 IBM 1401 assemblers.

"Memory corruption" existed, mostly as an entry on a "Things Not To Do" list.

On a Univac 1108 I found a hardware error where the first half-word fetch (the interrupt handler address) after a hardware interrupt would return all 1s, instead of the contents of the address. Off into the weeds, with interrupts disabled, no memory protect. Round and round it goes, where it stops nobody knows.

5 Peter-ReinstateMonica Jan 22 2021 at 19:31

You are comparing apples and pears. High level languages were invented because programs reached a size which was unmanageable with assembler. Example: "V1 had 4,501 lines of assembly code for its kernel, initialisation and shell. Of those, 3,976 account for the kernel, and 374 for the shell." (From this answer.)

The. V1. Shell. Had. 347. Lines. Of. Code.

Today's bash has maybe 100,000 lines of code (a wc over the repo yields 170k), not counting central libraries like readline and localization. High-level languages are in use partly for portability but also because it is virtually impossible to write programs of today's size in assembler. It's not just more error prone — it's nigh impossible.

4 supercat Jan 22 2021 at 03:45

I don't think memory corruption is generally more of a problem in assembly language than in any other language which uses unchecked array-subscripting operations, when comparing programs that perform similar tasks. While writing correct assembly code may require attention to details beyond those that would be relevant in a language like C, some aspects of assembly language are actually safer than C. In assembly language, if code performs a sequence of loads and stores, an assembler will produce load and store instructions in the order given without questioning whether they are all necessary. In C, by contrast, if a clever compiler like clang is invoked with any optimization setting other than -O0 and given something like:

extern char x[],y[];
int test(int index)
{
    y[0] = 1;
    if (x+2 == y+index)
        y[index] = 2;
    return y[0];
}

it may determine that the value of y[0] when the return statement executes will always be 1, and there's thus no need to reload its value after writing to y[index], even though the only defined circumstance where the write to index could occur would be if x[] is two bytes, y[] happens to immediately follow it, and index is zero, implying that y[0] would actually be left holding the number 2.

3 phyrfox Jan 23 2021 at 23:33

Assembler requires more intimate knowledge of the hardware you're using than other languages like C or Java. The truth is, though, assembler has been in use in almost everything from the first computerized cars, early video game systems up through the 1990's, up to the Internet-of-Things devices we use today.

While C offered type safety, it still didn't offer other safety measures like void pointer checking or bounded arrays (at least, not without extra code). It was quite easily to write a program that would crash and burn as well as any assembler program.

Tens of thousands of video games were written in assembler, compos to write small yet impressive demos in only a few kilobytes of code/data for decades now, thousands of cars still use some form of assembler today, as well as a few lesser-known operating systems (e.g. MenuetOS). You might have dozens or even hundreds of things in your house that were programmed in assembler that you don't even know about.

The main problem with assembly programming is that you need to plan more vigorously than you do in a language like C. It's perfectly possible to write a program with even 100k lines of code in assembler without a single bug, and it's also possible to write a program with 20 lines of code that has 5 bugs.

It's not the tool that's the problem, it's the programmer. I would say that memory corruption was a common problem in early programming in general. This was not limited to assembler, but also C (which was notorious for leaking memory and accessing invalid memory ranges), C++, and other languages where you could directly access memory, even BASIC (which had the ability to read/write specific I/O ports on the CPU).

Even with modern languages that do have safe-guards, we will see programming errors that crash games. Why? Because there's not enough care taken into designing the application. Memory management hasn't disappeared, it's been tucked into a corner where it's harder to visualize, causing all kinds of random havoc in modern code.

Virtually every language is susceptible to various kinds of memory corruption if used incorrectly. Today, the most common problem are memory leaks, which are easier than ever to accidentally introduce due to closures and abstractions.

It's unfair to say that assembler was inherently more or less memory-corrupting than other languages, it just got a bad rap because of how challenging it was to write proper code.

2 JohnDoty Jan 23 2021 at 02:12

It was a very common problem. IBM's FORTRAN compiler for the 1130 had quite a few: the ones I remember involved cases of incorrect syntax that weren't detected. Moving to machine-near higher level languages didn't obviously help: early Multics systems written in PL/I crashed frequently. I think that programming culture and technique had more to do with ameliorating this situation than language did.

2 JohnDallman Jan 24 2021 at 21:26

I did a few years of assembler programming, followed by decades of C. Assembler programs did not seem to have more bad pointer bugs than C, but a significant reason for that was that assembler programming is comparatively slow work.

The teams I was in wanted to test their work every time they'd written an increment of functionality, which was typically every 10-20 assembler instructions. In higher-level languages, you typically test after a similar number of lines of code, which have a lot more functionality. That trades off against the safety of a HLL.

Assembler stopped being used for large-scale programming tasks because it gave lower productivity, and because it usually wasn't portable to other kinds of computer. In the last 25 years I've written about 8 lines of assembler, and that was to generate error conditions for testing an error handler.

1 postasaguest Jan 22 2021 at 23:25

Not when I was working with computers back then. We had many problems but I never encountered memory corruption issues.

Now I worked on several IBM machines 7090,360,370,s/3,s/7 and also 8080 and Z80 based micros. Other computers may well have had memory problems.