Rust: مشكلة الذاكرة غير الآمنة في الصدأ الآمن

تم إنشاؤها على ١٧ فبراير ٢٠٢٠  ·  38تعليقات  ·  مصدر: rust-lang/rust

لدي برنامج صغير (تبسيط لوظيفة اختبار من مشروع أكبر) يقطع مصفوفة صغيرة ويحاول الوصول إلى عنصر خارج الحدود من الشريحة. تشغيله باستخدام cargo run --release باستخدام الإصدار المستقر 1.41.0 يطبع شيئًا كهذا (تم اختباره على macOS 10.15 و Ubuntu 19.10):

0 0 3 18446744073709551615
[1]    21065 segmentation fault  cargo run --release

يبدو أن الشريحة الناتجة لها طول 2**64 - 1 ، لذلك تم حذف التحقق من الحدود ، مما يؤدي بشكل متوقع إلى segfault. في 1.39.0 و 1.40.0 يطبع البرنامج نفسه ما كنت أتوقعه:

0 0 3 0
thread 'main' panicked at 'index out of bounds: the len is 0 but the index is 16777216', src/main.rs:13:35
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

تختفي المشكلة إذا قمت بأي مما يلي:

  • إزالة أي من مكالمتين do_test(...); في main() ؛
  • إزالة حلقة for _ in 0..1 { ؛
  • استبدل الحلقة for y in 0..x { بـ for y in 0..1 { ؛
  • أزل السطر z.extend(std::iter::repeat(0).take(x)); أو استبدله بـ z.extend(std::iter::repeat(0).take(1)); ؛
  • استبدل الحلقة for arr_ref in arr { بـ let arr_ref = &arr[0]; ؛
  • حدد RUSTFLAGS="-C opt-level=2" ؛
  • حدد RUSTFLAGS="-C codegen-units=1" .

أفضل تخمين لدي هو أن -C opt-level=3 يتيح تمرير تحسين إشكالي في LLVM ، مما يؤدي إلى سوء التجميع. يتم تأكيد ذلك من خلال حقيقة أن MIR ( --emit mir ) و LLVM IR قبل التحسينات ( --emit llvm-ir -C no-prepopulate-passes ) هو نفسه لكل من -C opt-level=2 و -C opt-level=3 .

بعض المعلومات الإضافية التي قد تكون مفيدة:

  • لا يمكنني إعادة إنتاج المشكلة في ملعب Rust (على الأرجح لأنه يستخدم codegen-units = 1 ) ؛
  • لا يمكنني إعادة إنتاج المشكلة على نظام التشغيل Windows 10 بنفس الإصدار 1.41.0 (لا توجد فكرة عما يجعلها مختلفة) ؛
  • cargo-bisect-rustc أن الانحدار حدث لأول مرة في 2019-12-12 ليلة ، وبالتحديد في هذا الالتزام . يبدو هذا مريبًا بالنسبة لي ، نظرًا لأن 1.40.0 ، والذي لا يظهر المشكلة ، تم إصداره بعد هذا التاريخ.

أنا أرفق البرنامج بشكل مضمن في حالة عدم عمل GitHub repo (إذا كنت تريد تجميعه بدون Cargo ، فاستخدم rustc -C opt-level=3 main.rs ):

fn do_test(x: usize) {
    let arr = vec![vec![0u8; 3]];

    let mut z = Vec::new();
    for arr_ref in arr {
        for y in 0..x {
            for _ in 0..1 {
                z.extend(std::iter::repeat(0).take(x));
                let a = y * x;
                let b = (y + 1) * x - 1;
                let slice = &arr_ref[a..b];
                eprintln!("{} {} {} {}", a, b, arr_ref.len(), slice.len());
                eprintln!("{:?}", slice[1 << 24]);
            }
        }
    }
}

fn main() {
    do_test(1);
    do_test(2);
}
A-LLVM C-bug I-unsound 💥 ICEBreaker-LLVM P-medium T-compiler regression-from-stable-to-stable

التعليق الأكثر فائدة

ناسخ الأشعة تحت الحمراء LLVM: https://gist.github.com/comex/881074b1bcc545e299e65527c719eef4

تشغيل opt bconfused.ll -scalar-evolution -loop-idiom -scalar-evolution -indvars -S -O3 -o - | grep xprint . إذا كان داخل الأقواس هو i64 -1 ، فقد حدث تحسين عربات التي تجرها الدواب. إذا لم يكن كذلك ... فقد لا يكون كذلك ، لكن من الصعب التأكد.

يبدو أنه أتى من LLVM بإضافة nuw إلى add nuw i64 %x, -1 بشكل غير صحيح كجزء من ممر Induction Variable Simplification. x هو الوسيطة للدالة ، و nuw يعني عدم وجود التفاف بدون توقيع ، لذلك يؤكد هذا بشكل فعال أن الوسيطة هي 0 ، في نقطة في الوظيفة حيث لا يكون مضمونًا.

التنصيف (تحرير: من LLVM 9 إلى LLVM 10 ، والذي قال tmiasko لم يتأثر) ينتج هذا الالتزام:

commit 58e8c793d0e43150a6452e971a32d7407a8a7401
Author: Tim Northover <[email protected]>
Date:   Mon Sep 30 07:46:52 2019 +0000

    Revert "[SCEV] add no wrap flag for SCEVAddExpr."

    This reverts r366419 because the analysis performed is within the context of
    the loop and it's only valid to add wrapping flags to "global" expressions if
    they're always correct.

    llvm-svn: 373184

يبدو واعدًا ، حيث تم تضمين r366419 (الالتزام الذي يعود الالتزام أعلاه) في LLVM 9.0 فرع يستخدم Rust.

ال 38 كومينتر

cc @ rust-lang / compiler
Rustbot ping icebreakers-llvm

يفكر فريق الإصدار في إصدار إصدار نقطي لـ Rust 1.41 (ناقشناه بإيجاز في اجتماع الأسبوع الماضي) ، وأود أن يتم تضمين هذا فيه إذا تمكنا من الحصول على العلاقات العامة قريبًا.

مرحبًا LLVM ICE-breakers! تم تحديد هذا الخطأ على أنه جيد
"مرشح لكسر الجليد LLVM". إذا كانت مفيدة ، فإليك بعضًا منها
[تعليمات] لمعالجة هذه الأنواع من الأخطاء. ربما نلقي نظرة؟
شكر! <3

سم مكعبcomexDutchGhost @ حنا وkruppehdhoangheyrutvik @ JOE1994jryansmmilenkonagisanikic @ نوح-كينيديSiavoshZarrasvandspastorinovertexcliquevgxbj

تشغيله مع تشغيل البضائع - حرر باستخدام الإصدار الثابت 1.41.0 يطبع شيئًا كهذا (تم اختباره على macOS 10.15 و Ubuntu 19.10):

لا يمكنني

تحرير: آه ، لقد قلت ذلك بالفعل.
كما أن البرنامج جيد في ميري ، لذلك من المحتمل ألا يكون هذا هو UB ولكنه خطأ في التجميع.

فقط لإضافة نقطة بيانات ، يمكنني إعادة إنتاج هذا على Linux مع أحدث إصدار كل ليلة:

[andrew<strong i="6">@krusty</strong> rust-69225]$ rustc --version
rustc 1.43.0-nightly (5e7af4669 2020-02-16)

[andrew<strong i="7">@krusty</strong> rust-69225]$ cat main.rs
fn do_test(x: usize) {
    let arr = vec![vec![0u8; 3]];

    let mut z = Vec::new();
    for arr_ref in arr {
        for y in 0..x {
            for _ in 0..1 {
                z.extend(std::iter::repeat(0).take(x));
                let a = y * x;
                let b = (y + 1) * x - 1;
                let slice = &arr_ref[a..b];
                eprintln!("{} {} {} {}", a, b, arr_ref.len(), slice.len());
                eprintln!("{:?}", slice[1 << 24]);
            }
        }
    }
}

fn main() {
    do_test(1);
    do_test(2);
}

[andrew<strong i="8">@krusty</strong> rust-69225]$ rustc -C opt-level=3 main.rs

[andrew<strong i="9">@krusty</strong> rust-69225]$ ./main
0 0 3 18446744073709551615
zsh: segmentation fault (core dumped)  ./main

تمكنت من إعادة إنتاج ما سبق بنفس الإخراج بالضبط مع استقرار Rust 1.41. الصدأ 1.40 مستقر لا يظهر المشكلة:

$ ./main
0 0 3 0
thread 'main' panicked at 'index out of bounds: the len is 0 but the index is 16777216', main.rs:13:35
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

أعتقد أن هذا يتوافق تمامًا مع تقرير dfyz ، إلا أن هذا يؤكد على الأقل أنه ليس خاصًا بنظام

  • cargo-bisect-rustc أن الانحدار حدث أولاً في 2019-12-12 ليلة ، تحديدًا في هذا الالتزام . يبدو هذا مريبًا بالنسبة لي ، نظرًا لأن 1.40.0 ، والذي لا يعرض المشكلة ، تم إصداره بعد هذا التاريخ.

هذا متوقع. تم إصدار 1.40.0 في 2019-12-19 استنادًا إلى ما كان يُعرف beta ، والذي تم تفرعه من master ستة أسابيع (في وقت قريب من الإصدار 1.39.0). راجع https://doc.rust-lang.org/book/appendix-07-nightly-rust.html لمزيد من المعلومات حول قنوات الإصدار.

إذا كان عليّ أن أخمن ، فسأقول أن https://github.com/rust-lang/rust/pull/67015 هو الجاني المحتمل. تم إصلاح هذه المشكلات الثلاثة الخاصة بـ codegen ، لذا فهي تلامس رمز التشفير الحرج.
Cc @ osa1 @ oli- obkwesleywiser

شكرا على ping. أقوم حاليًا بإنشاء تصميم للتحقيق. من المحتمل أن يكون هذا خطأ تم تقديمه في # 67015. الاحتمال الآخر هو أن # 67015 كشف للتو عن خطأ موجود.

تمكنت من إعادة إنتاج segfault باستخدام rustc ليلاً على نظام Linux ، ولكن ليس من خلال الإنشاء الذي تم إنشاؤه باستخدام config.toml هذا:

config.toml

[llvm]

[build]

[install]

[rust]
optimize = true
debug = true
codegen-units = 0
debug-assertions = true
debuginfo-level = 2

[target.x86_64-unknown-linux-gnu]
llvm-config = "/usr/bin/llvm-config-9"

[dist]

باستخدام rustc ليلا ، قمت بفحص MIRs قبل وبعد ConstProp وكانت MIRs متطابقة. لذلك إذا كان السبب في ذلك هو ConstProp ، فهذا بسبب الاختلاف في الشفرة التي تم إنشاؤها للمكتبة ، وليس هذا البرنامج.

الانحدار في 033662dfbca088937b9cdfd3d9584015b5e375b2

rustbot تعديل الملصقات: -E-needs-bisection


@ osa1 ربما يكون اللوم هو debug-assertions = true . عندما أحاول تجميع (باستخدام مترجم الفانيليا ليليًا) البرنامج باستخدام -C debug-assertions=y ، ينتاب البرنامج الذعر بدلاً من segfault

أعتقد أنني حللت ذلك! تؤدي العودة إلى a983e0590a43ed8b0f60417828efd4e79b51f494 إلى إصلاح المشكلة. بدا لي وكأنه الجاني طوال اليوم ، لكنني لم أتمكن من اختباره في العمل :) هل يمكن لأي شخص مساعدتي في أفضل طريقة لعمل علاقات عامة لهذه المشكلة؟ أعتقد أن أفضل طريقة هي إضافة اختبار يجب أن يفشل ، ولكن يبدو أن هذا خاص بالمنصة وما إلى ذلك ، فربما لا تكون هذه فكرة جيدة بعد كل شيء؟ أي أفكار هنا؟ شكر!

(تم تقسيم هذا بالفعل بواسطة OP)

تمكنت من إعادة إنتاج هذا باستخدام بنية محلية مع إيقاف تشغيل debug-assertions (شكرًا لذلك @ hellow554).

تتسبب بعض العلاقات العامة في مجموعة التحديثات في حدوث تعارضات عند التراجع لأن لدينا منذ ذلك الحين rustfmt -ed كل شيء ، لكنني أعتقد أن هذه المشكلة بسبب # 67174.

تحرير: يبدو أننا وجدنا هذا في نفس الوقت shahn :)

lqd نعم ، هذه هي المشكلة التي تتضمن الالتزام الذي أشرت إليه أعلاه. هذا هو الجاني.

لإضافة نقطة بيانات أخرى ، تختفي المشكلة عند تعيين codegen-units على 3 أو أقل (بافتراض ملف تعريف الإصدار ، مع incremental=false ). بمعنى آخر ، يمكنني التكاثر عندما يكون codegen-units 4 أو أكبر.

استدعاء panic_bounds_check يختفي بعد تمرير LLVM Jump Threading. يمكنني إعادة إنتاج المشكلة مع الاختيار من LLVM 9 ، ولكن ليس من LLVM 10.

لذلك ، قمت بسحب وإنشاء مرحلة 1 rustc ( ./x.py build -i --stage 1 ) ، أعدت بناء libstd ( ./x.py build -i --stage 1 --keep-stage 0 src/libstd ) بدون # 67174 ، وأعدت تجميع برنامج segfaulting باستخدام أربع وحدات كودجين ( rustc +stage1 -C opt-level=3 -C codegen-units=4 main.rs ). كما هو متوقع ، جعل هذا segfault يختفي. إذا قمت بتطبيق # 67174 مرة أخرى ، فسيتم إرجاع segfault.

هذا يعني أن لدي الآن مترجمين يختلفان فقط في المكتبة القياسية التي يستخدمونها. دعنا نسمي هذين المجمعين GOOD (no segfault) و BAD (segfault).

لاحظت بعد ذلك أن الملفات الأربعة _ unoptimized_ *.ll التي تم إنشاؤها بواسطة GOOD ( -C no-prepopulate-passes ) تشبه إلى حد كبير الملفات التي تم إنشاؤها بواسطة BAD (الاختلاف الوحيد لقد رأيت المعرفات العشوائية المختلفة في أسماء الوظائف) ، لكن ملفات _optimized_ *.ll (بدون -C no-prepopulate-passes ) مختلفة تمامًا. لست خبيرًا في المترجم بأي شكل من الأشكال ، ولكن نظرًا لأن البرنامج الذي يتم تجميعه هو نفسه تمامًا في كلتا الحالتين ، فإن كلا المترجمين المناسبين متماثلان تمامًا ، والفرق الوحيد هو في مكتبة قياسية مجمعة مسبقًا ، أعتقد أن LTO قد يكون متورطًا .

في الواقع ، إذا مررت بأي من -Z thinlto=no ، -C lto=no ، -C lto=yes ، -C lto=thin (في هذه المرحلة ، لست متأكدًا حقًا ، لكني أعتقد أن جميع النماذج من -C lto تختلف عن ThinLTO ، والذي يتم استخدامه افتراضيًا) إلى BAD ، يختفي segfault مرة أخرى.

هل يبدو من المحتمل أن يكون LTO هو الخطأ هنا؟

لقد قرأت حالة الاختبار. لقد قرأت الالتزام الذي يتم التراجع عنه. ما زلت لم أحصل على أكثر ما يحدث ضبابية. ما كسر؟

ما كسر؟

لا أعتقد في هذه المرحلة أنه يمكن لأي شخص أن يقول على وجه اليقين ما الذي حدث بالضبط ، ولكن تحليلي المبدئي هو هذا (من فضلك خذ ما يلي بحذر ، فأنا لست في فريق Rust أو فريق LLVM ، كل ما لدي يمكن القيام به هو العبث بالمترجم والتحديق في LLVM IR):

  • قمنا بإزالة عمليات التحقق من تجاوز السطر من سطر واحد في Layout::repeat() من المكتبة القياسية ، مما أدى في النهاية إلى عدم أمان الذاكرة. رياضياً ، يجب أن يكون استخدام إضافة غير محددة هنا آمنًا تمامًا - التعليق في هذه الوظيفة (وأيضًا في Layout::pad_to_align() ) يشرح السبب ؛
  • نموذج الكود الخاص بي الذي يوضح المشكلة لا يستدعي هذه الوظيفة ، لكنه يستخدم صراحة Vec ، والذي يستخدم ضمنيًا Vec::reserve_internal() ، والذي بدوره يستدعي Layout::repeat() ؛
  • تم تمييز Layout::repeat() كـ #[inline] ، ويبدو أن الاختلاف الوحيد ذي الصلة بين GOOD و BAD هو ما إذا كانت هذه الوظيفة مضمنة في do_test() أو ليس. على سبيل المثال ، تحظر استعادة عمليات التحقق من الفائض تضمين المشكلة وحلها ؛ إزالة السمة #[inline] تسبب نفس التأثير ؛ يؤدي تعطيل LTO إلى تعطيل التضمين لوظائف المكتبة وإصلاح المشكلة مرة أخرى.

إذا كان هذا صحيحًا (مرة أخرى ، لست متأكدًا بنسبة 100٪ من أي مما ورد أعلاه) ، فهذا يعني أن بعض تمريرات LLVM المارقة أو مجموعة من التمريرات تؤدي إلى إساءة استخدام IR بعد التضمين. هذا ما أحاول التحقيق فيه حاليًا ، ولكن للأسف ليس من السهل (على الأقل بالنسبة إلى غير قاطع LLVM-ICE مثلي) لأن IR يختلف بين GOOD و BAD معتدل كبير. يبدو أن الإصدار السيئ يغفل panic_bounds_check ، لكنني ما زلت غير متأكد تمامًا من السبب.

أيضًا ، مستوحاة من تعليق tmiasko ، حاولت تجميع rustc بإصدارات LLVM مختلفة ، ويبدو أن LLVM 10rc1 يحل المشكلة (أحدث إصدار LLVM الخاطئ الذي جربته هو 9.0.1). لا يبدو أن اللوم يقع على ممر Jump Threading ، لأنني لا أرى أي التزامات ذات صلة بين 9.0.1 و 10rc1.

إذا كان إرجاع التغيير Layout::repeat() يخفي فقط عرضًا لحدث واحد محدد لخلل غير ذي صلة ، فهل العودة فعلاً الشيء الصحيح الذي يجب فعله؟

إذا كان التراجع عن تغيير Layout :: المتكررة () يخفي فقط عرضًا لحدوث واحد محدد لخلل غير ذي صلة ، فهل حقًا الرجوع هو الشيء الصحيح الذي يجب فعله؟

أعتقد أنه قد يكون من الجيد إذا:

  • تم شحن التغيير
  • فهو يجعل تشغيل الخطأ أسهل بكثير ، مما يؤثر على العديد من المستخدمين
  • سوف يستغرق إصلاحه بشكل صحيح وقتًا طويلاً

إذا كان هذا التعليق ، فأعتقد أنني سأعيد التغيير ، وشحن إصدارًا بسيطًا لإلغاء حظر المستخدمين (عملت الأشياء بشكل جيد دون التغيير على الرغم من أن الخطأ كان لا يزال موجودًا) ، ثم ركز على الخطأ الفعلي.

في مترجم آخر أتذكر فعلاً هذا. لقد قمنا بإعادة تغيير في فرع التحرير ، ولكن ليس على المستوى الرئيسي (وهو ليس ممارسة جيدة ، فقد تسبب في حدوث مشكلات لاحقًا) ، وشحن إصدارًا ثانويًا جديدًا. ثم أصلح الخلل الفعلي.

على أي حال ، طالما أن الخطأ سيتم تحديد أولوياته ويتم إصلاحه ، والالتزام الذي يجعل الخطأ أسهل بكثير في التشغيل ليس إصلاحًا بحد ذاته ، لا أرى أي مشاكل في التراجع عنه في الوقت الحالي.

لذا فإن السؤال هو ، هل من السهل إثارة هذا الخطأ؟ حتى الآن ، كان لدينا تقرير واحد مع حالة اختبار متضمنة إلى حد ما حيث فشلت محاولة تقليل المزيد (مثل فتح حلقة for _ in 0..1 تافهة على ما يبدو) في إعادة الإنتاج.

لا أعتقد أنه من السهل تشغيل الخطأ ، يبدو أنني أصبحت محظوظًا بشكل خاص.

على أي حال ، إنني أقدر حقًا المشكلة التي مرت بها shahn لإرجاع تغيير Layout::new() ، لكن إرجاع IMO _ ليس هو الشيء الصحيح الذي يجب فعله في هذه الحالة. تفكيري (بالإضافة إلى ما قاله

  • إزالة عمليات التحقق من الفائض في Layout::repeat() تسمح لـ LLVM بتضمين Vec::reserve() في إصدارات الإصدار. قد يعطي أداءً جيدًا في بعض الحالات (على الرغم من أنه يجب قياس ذلك بالطبع) ؛
  • حرفيا الوظيفة السابقة في libcore/alloc.rs ( Layout::pad_to_align() ) تستخدم نفس نمط الإضافة غير المحددة مع نفس التعليق بالضبط الذي يشرح ما يجعل ذلك ممكنًا. يبدو أن استعادة عمليات التحقق الفائضة في Layout::repeat() ولكن ليس في Layout::pad_to_align() أمر غريب حقًا بالنسبة لي ؛
  • إذا تم حظر أي شخص بالفعل بشأن هذه المشكلة (أنا بالتأكيد لست كذلك) ، فهناك الكثير من الحلول الأخرى التي لا تتضمن تغييرات stdlib (على سبيل المثال ، تعطيل ThinLTO ، وتغيير مستوى التحسين ، وتقليل عدد وحدات الكوديجين).

ربما يتم إلقاء تأكيد دفاعي متضمن للإفراج محليًا عن الثابت كشرط مسبق بحيث يفزع بتفاصيل محددة لتعقب هذه الحالة المحددة أو بعض مصحح الأخطاء؟ أراهن على أنها عملية حسابية لم يتم التحقق منها في ظل ظروف معينة.

بعد ذلك ، عندما يتم تعقبها (في مكان ما في LLVM كما علمت للتو ، tydyfz) ، ستكون حالة اختبار الانحدار رائعة بحيث لا تحدث مرة أخرى. 🙏

ناسخ الأشعة تحت الحمراء LLVM: https://gist.github.com/comex/881074b1bcc545e299e65527c719eef4

تشغيل opt bconfused.ll -scalar-evolution -loop-idiom -scalar-evolution -indvars -S -O3 -o - | grep xprint . إذا كان داخل الأقواس هو i64 -1 ، فقد حدث تحسين عربات التي تجرها الدواب. إذا لم يكن كذلك ... فقد لا يكون كذلك ، لكن من الصعب التأكد.

يبدو أنه أتى من LLVM بإضافة nuw إلى add nuw i64 %x, -1 بشكل غير صحيح كجزء من ممر Induction Variable Simplification. x هو الوسيطة للدالة ، و nuw يعني عدم وجود التفاف بدون توقيع ، لذلك يؤكد هذا بشكل فعال أن الوسيطة هي 0 ، في نقطة في الوظيفة حيث لا يكون مضمونًا.

التنصيف (تحرير: من LLVM 9 إلى LLVM 10 ، والذي قال tmiasko لم يتأثر) ينتج هذا الالتزام:

commit 58e8c793d0e43150a6452e971a32d7407a8a7401
Author: Tim Northover <[email protected]>
Date:   Mon Sep 30 07:46:52 2019 +0000

    Revert "[SCEV] add no wrap flag for SCEVAddExpr."

    This reverts r366419 because the analysis performed is within the context of
    the loop and it's only valid to add wrapping flags to "global" expressions if
    they're always correct.

    llvm-svn: 373184

يبدو واعدًا ، حيث تم تضمين r366419 (الالتزام الذي يعود الالتزام أعلاه) في LLVM 9.0 فرع يستخدم Rust.

فرز T-compiler: P-medium ، بناءً على ملخص الحالة التالي:

pnkfelix: يبدو أن عناصر العمل المتبقية لـ # 69225 هي 1. إصلاح LLVM (إما عن طريق اختيار رقم 58e8c793d0e43150a6452e971a32d7407a8a7401 أو عن طريق الترقية إلى LLVM 10) ثم 2. readd PR # 67174.
pnkfelix: ولكن أيا من هذه العناصر لا تبدو لي كأولوية عالية.
pnkfelix: على الأقل ، لا يبدو خطأ LLVM هذا أفضل أو أسوأ من أخطاء برامج ترميز LLVM الأخرى. الذي أعتقد هو ما قاله @ simulacrum للتو.

تحديث: تتم محاولة الترقية إلى LLVM 10 في PR # 67759

التحديث 2: قد يكون من غير الحكمة اختيار التزام العودة بشكل أعمى ، نظرًا لأننا من المفترض أننا اخترنا الأصل لسبب ما ، وبالتالي قد يكون للعودة تأثيرات غير مقصودة في اتجاه مجرى النهر. على أقل تقدير ، لا يجب أن نحاول القيام بذلك دون فهم العواقب (وبالنظر إلى الجهود المبذولة للترقية إلى LLVM 10 ، فمن المحتمل ألا نحاول اختيار التراجع على الإطلاق ، لأنه سيكون جهدًا ضائعًا إلى حد كبير ...)

هل تم اختيار الكرز الأصلي؟ على الأقل من تعليق comex 'غير الواضح بالنسبة لي ("مضمن في LLVM 9.0 فرع يستخدم Rust" قد يعني أيضًا أنه مجرد جزء من LLVM 9.0).

الالتزام المعني هو تغيير محلي وصغير للغاية يضيف معلمة واحدة إلى استدعاء دالة ويقول حرفياً it is safe [in this case] to add SCEV::FlagNSW (واستناداً إلى الكود ، يمكن أن تكون المعلمة الجديدة أيضًا SCEV::FlagNUW ) ، لذلك أعتقد أنه من المحتمل جدًا أن هذا هو بالضبط سبب سوء التحسين. أستطيع أن أؤكد أن إزالة هذه المعلمة (أي تغيير (void)getAddRecExpr(getAddExpr(StartVal, Accum, Flags), Accum, L, Flags); إلى (void)getAddRecExpr(getAddExpr(StartVal, Accum), Accum, L, Flags); ) يحل المشكلة.

علاوة على ذلك ، فإن هذا الالتزام الإشكالي لم يتم انتقاؤه. إنه مجرد حظ سيء - يبدو أن العودة حدثت بعد إنشاء 9.0.0 ، لذلك لا يزال الإصدار 9.0.0 يحتوي على المعلمة المخالفة. وتعود لم backported إلى 9.0.1 سواء، لسبب ما. 10.0.0-rc1 والإصدارات الأحدث لها التراجع.

إليك تعليق يشرح سبب عدم أمان إضافة nsw أو nuw هنا ، في الواقع. ربما يكون من الجيد التحدث إلى مطور LLVM حول هذا الأمر ، لكنني أعتقد أن اختيار الكرز سيؤدي إلى حل هذه المشكلة ولن يكون له أي تأثيرات غير مقصودة على الإطلاق ، لأنه صغير جدًا ومكتفي بذاته.

ملاحظة: تنويه ضخم إلى

FWIW يمكنني أن أؤكد أن https://github.com/llvm/llvm-project/commit/58e8c793d0e43150a6452e971a32d7407a8a7401 آمن للاختيار من بينها ، إنه تغيير محافظ. راجع أيضًا https://lists.llvm.org/pipermail/llvm-dev/2019-September/135195.html إذا كنت مهتمًا بمزيد من السياق فيما يتعلق بمشكلة إشارات SCEV nowrap.

أعتقد أنني وجدت للتو طريقة لإعادة إنتاج المشكلة حتى بعد الرجوع إلى # 67174. إليك برنامج أطول قليلاً ، لكنه لا يزال آمنًا ، والذي يمكن الاعتماد عليه بشكل موثوق على أنظمة التشغيل Windows و Linux و macOS باستخدام أحدث البرامج كل ليلة مع إرجاع # 67174:

fn do_test(x: usize) {
    let mut arr = vec![vec![0u8; 3]];

    let mut z = vec![0];
    for arr_ref in arr.iter_mut() {
        for y in 0..x {
            for _ in 0..1 {
                z.reserve_exact(x);
                let iterator = std::iter::repeat(0).take(x);
                let mut cnt = 0;
                iterator.for_each(|_| {
                    z[0] = 0;
                    cnt += 1;
                });
                let a = y * x;
                let b = (y + 1) * x - 1;
                let slice = &mut arr_ref[a..b];
                slice[1 << 24] += 1;
            }
        }
    }
}

fn main() {
    do_test(1);
    do_test(2);
}

شبابيك:

PS> rustup run nightly rustc --version
rustc 1.43.0-nightly (6d0e58bff 2020-02-23)
PS> rustup run nightly cargo run --release
    Finished release [optimized] target(s) in 0.01s
     Running `target\release\rust-segfault.exe`
error: process didn't exit successfully: `target\release\rust-segfault.exe` (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)

لينكس:

$ rustup run nightly rustc --version
rustc 1.43.0-nightly (6d0e58bff 2020-02-23)
$ rustup run nightly cargo run --release
    Finished release [optimized] target(s) in 1.13s
     Running `target/release/rust-segfault`
Segmentation fault (core dumped)

macOS:

λ rustup run nightly rustc --version
rustc 1.43.0-nightly (6d0e58bff 2020-02-23)
λ rustup run nightly cargo run --release
    Finished release [optimized] target(s) in 0.01s
     Running `target/release/rust-segfault`
[1]    24331 segmentation fault  rustup run nightly cargo run --release

لا يعتمد هذا البرنامج على عدد وحدات الكوديجين ، لذا فهو سيقابل في الملعب أيضًا (على المستقر ، والتجريبي ، والليل). لقد قمت أيضًا بإعادة إنتاج هذا عن طريق تجميع rustc من الرئيسي (مع إرجاع # 67174) مرتبطًا بـ LLVM 9.

لا يزال خطأ LLVM الأساسي كما هو ، لذا فإن الترقية إلى LLVM 10 أو اختيار إصلاح LLVM يجعل الأمر يختفي.

أتمنى حقًا أن أفهم ما يحدث بشكل أفضل. يبدو أن عمليات التحقق من الحدود قد تم حذفها بسبب nuw الإضافي ، والذي يأتي من قيم SCEV المخزنة مؤقتًا بشكل غير صحيح (تمامًا كما هو الحال في برنامج C من مؤشر ترابط nikic المرتبط به). ولكن بحلول الوقت الذي يحدث فيه التحسين السيئ ، بالكاد يمكنني التعرف على برنامجي البسيط من خلال طبقات الكتل الأساسية LLVM ؛ الأسوأ من ذلك ، أن أي تغيير ظاهريًا في الكود المصدري (على سبيل المثال ، إزالة المتغير cnt ) يؤدي إلى ظهور LLVM IR بشكل مختلف تمامًا ويجعل المشكلة تختفي.

انطباعي هو أنه تم الانتهاء للتو من 1.41.1 في # 69359 (توقيت سيئ من جانبي) ، لذلك لا يوجد الكثير الذي يمكن القيام به في هذه المرحلة. هل من الجيد على الأقل تحديث التعليق في Layout::repeat() مع شرح أكثر تفصيلاً لمشكلة LLVM؟ إذا كان الأمر كذلك ، يمكنني إرسال PR.

انطباعي هو أنه تم الانتهاء للتو من 1.41.1 في # 69359 (توقيت سيئ من جانبي) ، لذلك لا يوجد الكثير الذي يمكن القيام به في هذه المرحلة.

إذا لم يؤد التصحيح الذي قمنا بتضمينه في 1.41.1 إلى حل المشكلة فعليًا ، فيجب علينا إعادة النظر فيما إذا كنا نريد نسخ الإصلاح الجديد وإعادة بناء الإصدار. وكان هناك توافق في اجتماع فريق الإفراج عدم backport الإصلاح LLVM، ولكن أنا شخصيا أعتقد أخرى هذا اضغط لتتحدث الجديد قد تضمن مناقشة أخرى حول هذا الموضوع.

cc @ Mark-Simulacrum @ rust-lang / release

dfyz سنحاول الحصول على نسخة أخرى من 1.41.1 مع إصلاح LLVM backported ، بينما ننتظر الإجماع على الشحن الفعلي لذلك.

FWIW ، بالنسبة لي ، يعمل الناسخ الجديد كما هو متوقع ( index out of bounds ) عند 1.38.0 ثابت وما قبله ، لكن segfaults عند 1.39.0 وما بعده. لا يوجد فرق كبير في LLVM بين 1.38 و 1.39 (https://github.com/rust-lang/llvm-project/compare/71fe7ec06b85f612fc0e4eb4134c7a7d0f23fac5....8adf9bdccfefb8d03f0e8db3b012f) على طول الطريق أيضا.

يعمل الناسخ الجديد كما هو متوقع (مؤشر خارج الحدود) على مستقر 1.38.0

اكتشفت (عن طريق الخطأ) أن إعداد -C codegen-units=1 على 1.38.0 يعيد إنتاج segfault. 1.37.0 يبدو آمنًا بالنسبة لي (لا ينتج عن مجموعة من الخيارات التي جربتها وجود segfault).

تجاهل ذلك ، 1.37.0 يستخدم LLVM 8.
من الغريب أن فرق LLVM IR بين 1.37.0 و 1.38.0 (مع -C codegen-units=1 ) هو سطر واحد فقط:

- %71 = icmp eq {}* %70, null
+ %71 = icmp ule {}* %70, null

(حيث يتم اشتقاق %70 من نتيجة <core::slice::IterMut<T> as core::iter::traits::iterator::Iterator>::next() )

هذا وحده يكفي لخداع LLVM لإضافة nuw اللعين إلى add nuw i64 %x, -1 .

1.37.0 يبدو آمنًا بالنسبة لي (لا ينتج عن مجموعة من الخيارات التي جربتها وجود segfault).

يستخدم هذا LLVM 8 ، لذلك لا ينبغي أن يكون تغيير SCEV الذي تم إلقاء اللوم عليه موجودًا على الإطلاق.

هذا باستخدام LLVM 8

بلدي السيئ ، آسف للارتباك (كنت سعيدًا جدًا لتقليصه إلى فرق من سطر واحد لم أزعج نفسي بالتحقق من إصدار LLVM).

قمنا بإعداد 1.41.1 قطعة أثرية جديدة مع LLVM fix Cherry الذي تم اختياره فيه. يمكنك اختبارها محليًا باستخدام:

RUSTUP_DIST_SERVER=https://dev-static.rust-lang.org rustup update stable

ping في https://github.com/rust-lang/rust/issues/69225#issuecomment -586941455

[triagebot] تم حل المشكلة بنجاح دون أي تدخل من فريق المترجم الذي تم اختباره.
جيد جدا.

1.41.1 خارج ، أعتقد أن الوقت قد حان لإغلاق هذه المشكلة في النهاية.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات