Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions text/0000-إرخاء-حجب-رموز-حارس-المكدّس.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
- **عنوان المقترح:** إرخاء حارس تصادم `@رمز` لرمز حارس المكدّس `__stack_chk_guard` في **الوضع الحرّ** حصرًا
- **النطاق:** لغة `text/`
- **تاريخ البدء:** 2026-07-22
- **رقم الـ RFC:** 239 (= رقم PR اللغة)
- **الحالة:** مقترَح — التنفيذ مدموج في s-programming-language#239 (وثيقة قرار)
- **Issue التتبُّع:** (يُترك فارغاً حتى القبول)

# ملخّص

حارس تصادم رمز `@رمز` المُصدَّر (`SEM022 = SEM_SYMBOL_NAME_CONFLICT`، اللبنة
3.17) يحجب — على **المتغيّرات العامّة** — قائمةَ رموزٍ محجوزة لزمن التشغيل تضمّ
`__stack_chk_guard`. يقترح هذا المقترح **إرخاء `__stack_chk_guard` في الوضع
الحرّ فقط** (يبقى محجوبًا في المستضاف)، مع إبقاء بقيّة القائمة محجوبةً في
الوضعين (`memset`/`malloc`/`__divdi3`/`__sad_heap`…).

الأثر المرئيّ: يستطيع برنامجٌ في الوضع الحرّ (نواة، مُقلِع) توفير رمز حارس المكدّس
الذي يطلبه `-fstack-protector` **بلغة ص** عبر `@رمز("__stack_chk_guard") متغير …`،
بدل إبقائه بيانةً بلغة C. هذا يُكمِل هجرة آليّة حارس الإطار إلى ص (كان المعالج
`__stack_chk_fail` قد هاجر في أخ.3 عبر مسار الدوالّ المنفصل).

# الدافع (Motivation)

حملة «إطفاء C» في نواة النحلة تهاجر كلّ ما يُعبَّر عنه بـص من طبقة التراب C إلى
ص. عند محاولة هجرة بيانات `__stack_chk_guard` (أخ.4) عبر:

```
@رمز("__stack_chk_guard") متغير حارس_إطار_المترجم = 3202730513
```

يرفض المترجم في الوضع الحرّ:

```
error: (AR) @رمز("__stack_chk_guard"): اسم محجوز لزمن التشغيل المضمَّن — اختر اسمًا آخر
```

بينما هاجر **المعالج** `__stack_chk_fail` بنجاح في أخ.3، لأنّ `@رمز` على
**الدالّة** يمرّ بمسار توليدٍ آخر لا يستشير هذه القائمة (الخاصّة بالمتغيّرات).
فنشأ حجبٌ يمنع استعمالًا مشروعًا في الوضع الحرّ.

## لماذا الحجب زائد في الوضع الحرّ (لا في المستضاف)

القائمة تحرس **مصدرَي تصادم**:

1. **رمز يُصدر المترجم له احتياطيًّا ضعيفًا** (`weak`): لكنّ إصدار هذه الرموز
محصورٌ بالوضع الحرّ (`emitFreestandingRuntime` تحت `cg_.freestanding_`).
`@رمز` عليها = تعريف ثالث ⇒ تصادم فيختار الرابط عشوائيًّا.
2. **رمز منصّة توفّره libc** في الوضع المستضاف (`memset`/`__stack_chk_guard`…):
`@رمز` قويّ عليه يقنّع رمز libc — إمّا خطأ ربط، أو **تقنيع صامت** لكوكي SSP
العشوائيّ بقيمة ثابتة ⇒ تعطيل حماية تحطّم المكدّس للعمليّة كلّها.

تحقُّق فعليّ: `__stack_chk_guard` **غير مُصدَر** من المترجم/الرنتايم إطلاقًا
(`grep` عبر `compiler/src` و`runtime/` = صفر) ⇒ لا احتياطيّ ضعيف. إذًا في الوضع
الحرّ لا مصدر (١) ولا (٢) — `@رمز` عليه هو التعريف **الوحيد** فلا تصادم، والحجب
زائد. أمّا في المستضاف فمصدر (٢) قائم (libc) ⇒ الحجب صحيح ويبقى.

لذا الإرخاء **مشروط بالوضع الحرّ**، لا إزالةٌ مطلقة من القائمة.

# الشرح التوجيهي (Guide-level explanation)

**قبل:** `__stack_chk_guard` بيانة C ثابتة في طبقة التراب:

```c
unsigned long __stack_chk_guard = 0xBEE5CA11uL;
```

**بعد (وضع حرّ):** بيانة ص عبر `@رمز`، ويُبقي التراب إعلان `extern` فقط ليربطه
المولَّد:

```
# ص
@رمز("__stack_chk_guard") متغير حارس_إطار_المترجم = 3202730513 # 0x‏BEE5CA11
```
```c
/* C */ extern unsigned long __stack_chk_guard;
```

في المستضاف يبقى `@رمز("__stack_chk_guard")` محجوبًا (libc تملكه)، تمامًا كـ
`memset`. `__stack_chk_fail` **دالّة**: ربط **بيانة** باسمها يبقى محجوبًا في
الوضعين (نداء كائن بيانات ⇒ تعطّل)؛ ومسار `@رمز` على الدالّة منفصل غير متأثّر.

⚠️ يجب أن يكون الحارس **`متغير`** لا `ثابت`: `ثابت` يُصدَر `constant` في
`.rodata` (للقراءة فقط)، بينما SSP يعامل `__stack_chk_guard` ككائن بيانات قابل
للكتابة (قد يُحدَّث/يُقرأ)؛ `متغير` يُصدَر `global` قابلًا للكتابة في `.data`.

# الشرح المرجعيّ (Reference-level explanation)

- الموضع: `compiler/src/backend/llvm/builders/oop/classes_vtables_ops.cpp`،
مسار إصدار المتغيّرات العامّة (`hasLinkSym`).
- التصميم مجموعتان:
- `kReservedRt` (محجوزة **دائمًا** — حرّ ومستضاف): بقيّة القائمة +
`__stack_chk_fail` (اسم دالّة؛ ربط بيانة به غير مشروع).
- `kHostedOnlyReserved` (محجوزة في **المستضاف فقط**): `__stack_chk_guard`.
- الشرط: `kReservedRt.count(sym) || (!cg_.freestanding_ && kHostedOnlyReserved.count(sym))`.
- حراسة التكرار الفعليّ (`getNamedValue`) والرابط تبقى قائمةً فوق ذلك.
- ملاحظة حجم ABI (**قيد صريح**، تخصّ المستهلك لا اللغة): متغيّر ص = `i64` دومًا،
فتُصدَر `@__stack_chk_guard = global i64` (8 بايت)، بينما SSP يقرأ الحارس
`uintptr_t` (4 بايت على i686). البايتات الدنيا هي الحارس على **little-endian**،
والقيمة تُهيَّأ وقت التحميل في `.data` ⇒ لا نافذة حارس صفريّ. **الحاجز:** هذا
السبيل صالح لهدف **i686 صغير-البايتيّة (LE) في الوضع الحرّ حصرًا**؛ أهداف
big-endian أو فحوص تعتمد `sizeof(__stack_chk_guard)==4` تنكسر. اللغة لا تملك
حاليًّا بدائيّة لإصدار عالميّ 32-بت — عند الحاجة لهدف آخر تُضاف كـRFC منفصل.

# البدائل (Alternatives)

1. **إبقاء الحجب وترك `__stack_chk_guard` بيانةَ C:** يُبقي ذيلَ C لآليّة هاجر
معالجُها (`__stack_chk_fail`) إلى ص، بلا مبرّر تقنيّ.
2. **إزالة `__stack_chk_guard` من القائمة مطلقًا (بلا شرط الوضع الحرّ):** يفتح في
المستضاف تعريفًا قويًّا يقنّع كوكي SSP لـlibc صامتًا (ثغرة أمنيّة). **مرفوض**؛
شرط `cg_.freestanding_` هو ما يغلق هذا.
3. **السماح بكلّ الأسماء المحجوزة عند `@رمز` الصريح:** يفقد شبكة الأمان ضدّ
مصادفة `memset`/`malloc`. مرفوض.

# المخاطر والتخفيف

- **تقنيع libc-SSP في المستضاف (صامت):** لو أُرخي بلا شرط، يُصدر تعريفًا قويًّا
يستبدل الكوكي العشوائيّ بقيمة ثابتة بلا خطأ ربط (ربط ديناميكيّ) ⇒ تعطيل الحماية.
التخفيف: الإرخاء مشروط بـ`cg_.freestanding_` (مُختبَر: يُرفض في المستضاف).
- **تعريف مزدوج قويّ** (ص + C في الوضع الحرّ): يرصده الرابط بخطأ «duplicate
symbol» ظاهر. أخ.4 تحذف تعريف C وتُبقي `extern` ⇒ تعريف قويّ وحيد.
- **قيد قائم مكتشَف (خارج نطاق هذا المقترح):** خروج المترجم يبقى `0` في
**المستضاف** لأخطاء هذه المرحلة (حجب `memset`/`__stack_chk_guard`): الرمز لا
يُصدَر (‏`continue`) لكنّ رمز الخروج لا يُرفع. سلوك يخصّ كلّ الأسماء المحجوزة لا
هذا التغيير؛ يُقترح إصلاحه لاحقًا (رفع رمز خروج لأخطاء codegen في المستضاف).

# الاختبارات

- `test_symbol_stack_guard_allowed_freestanding`: يترجم بنجاح ويُصدر
`@__stack_chk_guard = global i64`.
- `test_symbol_stack_guard_rejected_hosted`: في المستضاف تظهر رسالة الحجب و**لا**
يُصدَر الرمز (حارس الثغرة الأمنيّة).
- `test_symbol_stack_chk_fail_var_rejected`: ربط بيانة باسم الدالّة محجوب في
الوضعين.
- `test_symbol_memset_rejected_hosted` + `test_symbol_reserved_runtime_name_errors`
(قائم): `memset` يبقى محجوبًا في الوضعين — إثبات حصر الإرخاء.
- `test_symbol_stack_guard_duplicate_freestanding_errors`: تكرار الرمز المُرخَّى
في الوضع الحرّ يبقى خطأً (حارس `getNamedValue` فعّال).

# الأثر على مصدر الحقيقة

لا تغيير في نحو اللغة ولا في كتالوج المفردات؛ التغيير سلوكُ حارسٍ دلاليّ في
الخلفيّة (codegen) فقط. لا أثر على LSP/التوثيق المولَّد.
Loading