ومرة أخرى عن المضمنة: البحث عن الأخطاء في مشروع Embox

الشكل 2

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

قبل بضعة أشهر ، كتبت بالفعل مقالة حول التحقق من FreeRTOS ، نظام تشغيل آخر للأنظمة المضمنة. لم أجد أخطاء في ذلك الوقت ، لكني وجدتها في مكتبات أضافها شباب أمازون عند تطوير نسختهم الخاصة من FreeRTOS.

تستمر المقالة التي تراها الآن أمامك ، إلى حد ما ، في موضوع المقالة السابقة. غالبًا ما تلقينا طلبات للتحقق من FreeRTOS ، وقمنا بذلك. هذه المرة ، لم تكن هناك طلبات للتحقق من مشروع معين ، لكنني بدأت في تلقي رسائل وتعليقات من المطورين المضمنين الذين أحبوه والذين يريدون المزيد.

حسنًا ، لقد نضج الإصدار الجديد من مجلة PVS-Studio للمجلة المضمنة ويكمن أمامك. استمتع بالمشاهدة!

تحليل


تم إجراء التحليل باستخدام PVS-Studio - محلل الكود الثابت لـ C و C ++ و C # و Java. قبل التحليل ، يجب تجميع المشروع - لذلك سنكون على يقين من أن رمز المشروع يعمل ، وسنمنح المحلل أيضًا الفرصة لجمع معلومات التجميع التي يمكن أن تكون مفيدة للتحقق من التعليمات البرمجية بشكل أفضل.

توفر الإرشادات الموجودة في مستودع Embox الرسمي القدرة على الإنشاء تحت أنظمة مختلفة (Arch Linux و macOS و Debian) واستخدام Docker. قررت أن أضيف بعض التنوع إلى حياتي وأن أبني وأحلل من تحت دبيان ، والذي قمت بتثبيته مؤخرًا على جهازي الافتراضي.

ذهب التجميع بسلاسة. الآن كان من الضروري إجراء تحليل. Debian هو أحد الأنظمة المعتمدة على نظام PVS-Studio Linux. طريقة ملائمة للتحقق من المشاريع من تحت Linux هي تجميع التتبع. هذا وضع خاص يقوم فيه المحلل بجمع جميع المعلومات الضرورية حول التجميع بحيث يمكنك بعد ذلك بدء التحليل بنقرة واحدة. كل ما كنت بحاجة إلى القيام به هو:

1) تنزيل وتثبيت PVS-Studio ؛

2) قم بتشغيل تتبع التجميع بالانتقال إلى المجلد باستخدام Embox والكتابة في النهاية الطرفية

pvs-studio-analyzer analyze -- make

3) بعد انتظار اكتمال التجميع ، قم بتشغيل الأمر:

pvs-studio-analyzer analyze -o /path/to/output.log

4) تحويل التقرير الأولي إلى أي تنسيق مناسب لك. يأتي المحلل مع أداة خاصة PlogConverter ، والتي يمكنك من خلالها القيام بذلك. على سبيل المثال ، سيبدو الأمر الخاص بتحويل التقرير إلى قائمة المهام (للعرض ، على سبيل المثال ، في QtCreator) كما يلي:

plog-converter -t tasklist -o /path/to/output.tasks /path/to/project

الكل! لم يستغرق الأمر مني أكثر من 15 دقيقة لإكمال هذه النقاط. التقرير جاهز ، الآن يمكنك عرض الأخطاء. حسنًا ، لنبدأ :)

دورة غريبة


كان أحد الأخطاء التي وجدها المحلل هو حلقة غريبة بينما:

int main(int argc, char **argv) {
  ....

  while (dp.skip != 0 ) {
    n_read = read(ifd, tbuf, dp.bs);
    if (n_read < 0) {
      err = -errno;
      goto out_cmd;
    }
    if (n_read == 0) {
      goto out_cmd;
    }

    dp.skip --;
  } while (dp.skip != 0);       // <=

  do {
    n_read = read(ifd, tbuf, dp.bs);
    if (n_read < 0) {
      err = -errno;
      break;
    }

    if (n_read == 0) {
      break;
    }

    ....

    dp.count --;
  } while (dp.count != 0);
  ....
}

تحذير PVS-Studio: V715 يكون عامل التشغيل "while" فارغًا. تم اكتشاف نمط مريب: 'while (expr) {...} بينما (dp.skip! = 0)؛'. DD ج ج 225

. حلقة غريبة حقا. يتم كتابة تعبير while (dp.skip! = 0) مرتين ، مرة واحدة أعلى الحلقة مباشرةً ، والثاني أسفلها مباشرةً. في الواقع ، هذه دورتان مختلفتان: واحدة تحتوي على تعبيرات بأقواس متعرجة ، والثانية فارغة. في هذه الحالة ، لن يتم تنفيذ الدورة الثانية.

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

تسريبات الذاكرة


نعم ، ليس بدونهم.

int krename(const char *oldpath, const char *newpath) {
  
  char *newpatharg, *oldpatharg;

  ....

  oldpatharg =
    calloc(strlen(oldpath) + diritemlen + 2, sizeof(char));
  newpatharg =
    calloc(strlen(newpath) + diritemlen + 2, sizeof(char));
  if (NULL == oldpatharg || NULL == newpatharg) {
    SET_ERRNO(ENOMEM);
    return -1;
  }

  ....
}

تحذيرات PVS-Studio:

  • V773 The function was exited without releasing the 'newpatharg' pointer. A memory leak is possible. kfsop.c 611
  • V773 The function was exited without releasing the 'oldpatharg' pointer. A memory leak is possible. kfsop.c 611

تقوم الوظيفة بإنشاء المتغيرات المحلية newpatharg و oldpatharg بداخلها . يتم تعيين هذه المؤشرات لعناوين مواقع الذاكرة الجديدة المخصصة داخليًا باستخدام calloc . في حالة حدوث مشكلة عند تخصيص الذاكرة ، يقوم calloc بإرجاع مؤشر فارغ.

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

مثال آخر على تسرب الذاكرة أكثر إشراقًا:

static int block_dev_test(....) {
  int8_t *read_buf, *write_buf;
  
  ....

  read_buf = malloc(blk_sz * m_blocks);
  write_buf = malloc(blk_sz * m_blocks);

  if (read_buf == NULL || write_buf == NULL) {
    printf("Failed to allocate memory for buffer!\n");

    if (read_buf != NULL) {
      free(read_buf);
    }

    if (write_buf != NULL) {
      free(write_buf);
    }

    return -ENOMEM;
  }

  if (s_block >= blocks) {
    printf("Starting block should be less than number of blocks\n");
    return -EINVAL;            // <=
  }

  ....
}

تحذيرات PVS-Studio:

  • V773 The function was exited without releasing the 'read_buf' pointer. A memory leak is possible. block_dev_test.c 195
  • V773 The function was exited without releasing the 'write_buf' pointer. A memory leak is possible. block_dev_test.c 195

هنا ، أظهر المبرمج بالفعل الدقة وعالج الحالة التي كان من الممكن فيها تخصيص جزء واحد فقط من الذاكرة بشكل صحيح. معالجتها بشكل صحيح ... حرفيا في التعبير التالي ارتكب خطأ آخر.

بفضل شيك مكتوب بشكل صحيح ، يمكننا التأكد من أنه في وقت تنفيذ التعبير ، ارجع -EINVAL ؛ سيكون لدينا بالتأكيد ذاكرة مخصصة لكل من read_buf و write_buf . وبالتالي ، مع مثل هذه العودة من الوظيفة ، سيكون لدينا تسريبان في وقت واحد.

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

سوء التعامل مع المؤشرات


رمز الخطأ التالي موجز وبسيط بما فيه الكفاية:

static int scsi_write(struct block_dev *bdev, char *buffer,
                      size_t count, blkno_t blkno) {
  struct scsi_dev *sdev;
  int blksize;

  ....

  sdev = bdev->privdata;
  blksize = sdev->blk_size; // <=

  if (!sdev) {              // <=
    return -ENODEV;
  }

  ....
}

تحذير PVS-Studio: V595 تم استخدام مؤشر "sdev" قبل التحقق من صحته باستخدام nullptr. فحص الأسطر: 116 ، 118. scsi_disk.c 116

يتم إلغاء الإشارة إلى مؤشر sdev قبل التحقق من NULL. من المنطقي افتراض أنه إذا كتب شخص ما مثل هذا الشيك ، فقد يكون هذا المؤشر فارغًا. في هذه الحالة ، لدينا احتمال إعادة الإشارة إلى المؤشر الفارغ في السلسلة blksize = sdev-> blk_size .

الخطأ هو أن الشيك غير موجود حيث هو مطلوب. كان يجب أن يأتي بعد السطر " sdev = bdev-> privdata؛ " ، ولكن قبل السطر " blksize = sdev-> blk_size؛ ". ثم يمكن تجنب الاستئناف المحتمل على العنوان صفر.

عثر PVS-Studio على خطأين آخرين في التعليمات البرمجية التالية:

void xdrrec_create(....)
{
  char *buff;

  ....

  buff = (char *)malloc(sendsz + recvsz);
  assert(buff != NULL);

  ....

  xs->extra.rec.in_base = xs->extra.rec.in_curr = buff;
  xs->extra.rec.in_boundry 
    = xs->extra.rec.in_base + recvsz;                    // <=

  ....
  xs->extra.rec.out_base
    = xs->extra.rec.out_hdr = buff + recvsz;             // <= 
  xs->extra.rec.out_curr 
    = xs->extra.rec.out_hdr + sizeof(union xdrrec_hdr);

  ....
}

تحذيرات PVS-Studio:

  • V769 يمكن أن يكون مؤشر 'xs-> extra.rec.in_base' في تعبير 'xs-> extra.rec.in_base + recvsz' nullptr. في مثل هذه الحالة ، ستكون القيمة الناتجة لا معنى لها ولا يجب استخدامها. تحقق من الخطوط: 56 ، 48. xdr_rec.c 56
  • V769 يمكن أن يكون المؤشر "buff" في تعبير "buff + recvsz" فارغًا. في مثل هذه الحالة ، ستكون القيمة الناتجة لا معنى لها ولا يجب استخدامها. خطوط التحقق: 61 ، 48. xdr_rec.c 61

تتم تهيئة مؤشر buf باستخدام malloc ، ثم يتم استخدام قيمته لتهيئة مؤشرات أخرى. يمكن أن تقوم الدالة malloc بإرجاع مؤشر فارغ ، ويجب دائمًا التحقق من ذلك. ويبدو أن هنا هو ASSERT فحص BUF ل NULL ، وكل شيء يجب أن تعمل بشكل جيد.

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

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

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

معالجة غير صحيحة للصفائف


الخطأ التالي مشابه جدًا للمثال قبل الأخير:


int fat_read_filename(struct fat_file_info *fi,
                      void *p_scratch,
                      char *name) {
  int offt = 1;

  ....

  offt = strlen(name);
  while (name[offt - 1] == ' ' && offt > 0) { // <=
    name[--offt] = '\0';
  }
  log_debug("name(%s)", name);

  return DFS_OK;
}

تحذير PVS-Studio: V781 يتم التحقق من قيمة فهرس 'offt' بعد استخدامه. ربما هناك خطأ في منطق البرنامج. fat_common.c 1813 يتم استخدام offt

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

الصورة 1

الظروف المشبوهة


وأين بدونهم؟ نجد مثل هذه الأخطاء حرفيا في كل مشروع نتحقق منه.

int index_descriptor_cloexec_set(int fd, int cloexec) {
  struct idesc_table *it;

  it = task_resource_idesc_table(task_self());
  assert(it);

  if (cloexec | FD_CLOEXEC) {
    idesc_cloexec_set(it->idesc_table[fd]);
  } else {
    idesc_cloexec_clear(it->idesc_table[fd]);
  }
  return 0;
}

تحذير PVS-Studio: V617 فكر في فحص الحالة. وسيطة "0x0010" الخاصة بـ "|" تحتوي عملية bitwise على قيمة غير صفرية. index_descriptor.c 55

لفهم ما يكمن في الخطأ ، انظر إلى تعريف ثابت FD_CLOEXEC :

#define FD_CLOEXEC 0x0010

اتضح أنه في التعبير إذا (cloexec | FD_CLOEXEC) على يمين البت "أو" يوجد دائمًا ثابت غير صفري. ستكون نتيجة هذه العملية دائمًا رقمًا غير صفري. وبالتالي ، سيكون هذا التعبير معادلًا دائمًا للتعبير إذا (صحيحًا) ، وسنعالج دائمًا الفرع حينها فقط من التعبير إذا.

أظن أن ثابت الماكرو هذا يُستخدم للتهيئة المسبقة لنظام تشغيل Embox ، ولكن حتى في هذه الحالة ، تبدو هذه الحالة الحقيقية دائمًا غريبة. ربما أرادوا استخدام و المشغل هنا ، لكنها ارتكبت خطأ إملائيا.

تقسيم صحيح


يرتبط الخطأ التالي بميزة واحدة للغة C:

#define SBSIZE    1024

static int ext2fs_format(struct block_dev *bdev, void *priv) {
  size_t dev_bsize;
  float dev_factor;

  ....

  dev_size = block_dev_size(bdev);
  dev_bsize = block_dev_block_size(bdev);
  dev_factor = SBSIZE / dev_bsize;            // <=

  ext2_dflt_sb(&sb, dev_size, dev_factor);
  ext2_dflt_gd(&sb, &gd);

  ....
}

تحذير PVS-Studio: V636 تم التعبير عن التعبير "1024 / dev_bsize" ضمنيًا من النوع "int" إلى النوع "float". ضع في اعتبارك استخدام قالب صريح لتجنب فقدان جزء كسري. مثال: double A = (double) (X) / Y؛. ext2.c 777

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

في بعض الأحيان ينسى المبرمجون ذلك ، ويتم ارتكاب مثل هذه الأخطاء. SBSIZE ثابت ومتغير dev_bsize لهما نوع صحيح (int و size_t على التوالي). لذلك ، نتيجة التعبير SBSIZE / dev_bsizeسيكون أيضًا من النوع الصحيح.

لكن انتظر لحظة. المتغير dev_factor من النوع العائم ! من الواضح أن المبرمج يتوقع أن يحصل على نتيجة قسمة كسرية. يمكن التحقق من ذلك أيضًا إذا انتبهت إلى الاستخدام الإضافي لهذا المتغير. على سبيل المثال ، تحتوي الدالة ext2_dflt_sb ، حيث يتم تمرير dev_factor كمعلمة ثالثة ، على التوقيع التالي:

static void ext2_dflt_sb(struct ext2sb *sb, size_t dev_size, float dev_factor);

وبالمثل ، في أماكن أخرى حيث يتم استخدام متغير dev_factor : كل ​​شيء يشير إلى توقع رقم الفاصلة العائمة.

لتصحيح هذا الخطأ ، يكفي فقط تحويل أحد معاملات القسمة إلى نوع حقيقي. على سبيل المثال:

dev_factor = float(SBSIZE) / dev_bsize;

ثم ستكون نتيجة القسمة عدد كسري.

إدخال لم يتم التحقق منه


يرتبط الخطأ التالي باستخدام البيانات التي لم يتم التحقق منها والتي تم تلقيها من خارج البرنامج.

int main(int argc, char **argv) {
  int ret;
  char text[SMTP_TEXT_LEN + 1];

  ....

  if (NULL == fgets(&text[0], sizeof text - 2, /* for \r\n */
      stdin)) { ret = -EIO; goto error; }
    text[strlen(&text[0]) - 1] = '\0'; /* remove \n */    // <=

  ....
}

تحذير PVS-Studio: V1010 يتم استخدام البيانات غير المحددة التي لم يتم التحقق منها في الفهرس: 'strlen (& text [0])'. sendmail.c 102

أولاً ، ضع في اعتبارك ما تعيده الدالة fgets . في حالة القراءة الناجحة لخط ما ، تقوم الدالة بإرجاع مؤشر إلى هذا الخط. إذا واجه القراءة ل نهاية الملف قبل أن يتم قراءة عنصر واحد على الأقل، أو في حالة حدوث خطأ في إدخال البيانات، و fgets تعمل عوائد NULL .

لذلك التعبير هو NULL == fgets (....)للتحقق من صحة المدخلات المستلمة. ولكن هناك تحذير واحد. إذا قمت بنقل محطة طرفية فارغة كأول حرف يجب قراءته (يمكن القيام بذلك ، على سبيل المثال ، عن طريق الضغط على Ctrl + 2 في الوضع القديم لسطر أوامر Windows) ، فإن وظيفة fgets تعتبرها دون إرجاع NULL . في هذه الحالة ، سيكون للخط الذي يتم التسجيل إليه عنصر واحد فقط - " \ 0 ".

ماذا سيحدث بعد؟ ستظهر عبارة strlen (& text [0]) 0 . ونتيجة لذلك ، نحصل على مكالمة مؤشر سلبي:

text[ 0 - 1 ] = '\0';

ونتيجة لذلك ، يمكننا "قلب" البرنامج ببساطة عن طريق تمرير حرف إنهاء السطر إلى الإدخال. هذا أمر محرج ، ويمكن استخدامه لمهاجمة الأنظمة باستخدام Embox.

زميلي ، الذي كان يطور قاعدة التشخيص هذه ، سجل ملاحظة بمثال على مثل هذا الهجوم على مشروع NcFTP:


أوصي لمعرفة ما إذا كنت لا تزال لا تؤمن بهذه الفرصة :)

أيضًا ، وجد المحلل مكانين آخرين بهما نفس الخطأ:

  • V1010 يتم استخدام البيانات غير المحددة التي لم يتم التحقق منها في الفهرس: 'strlen (& from [0])'. أرسل بريد ج 55
  • V1010 يتم استخدام البيانات غير المحددة التي لم يتم التحقق منها في الفهرس: "strlen (& [0])". م. 65

ميسرا


MISRA هي مجموعة من الإرشادات والمبادئ التوجيهية لكتابة كود C و C ++ آمن للأنظمة المدمجة عالية المسؤولية. بمعنى ما ، هذا دليل تدريبي ، سيسمح لك بعد ذلك بالتخلص ليس فقط من "رائحة الكود" المزعومة ، ولكن أيضًا حماية برنامجك من نقاط الضعف.

تستخدم ميسرا حيث تعتمد حياة البشر على جودة نظامك المدمج: في الصناعات الطبية والسيارات والطائرات والصناعات العسكرية.

لدى PVS-Studio مجموعة شاملة من قواعد التشخيص التي تسمح لك بالتحقق من الكود الخاص بك للتأكد من توافقه مع معايير MISRA C و MISRA C ++. بشكل افتراضي ، يتم إيقاف تشغيل الوضع مع هذه التشخيصات ، ولكن نظرًا لأننا نبحث عن أخطاء في المشروع للأنظمة المضمنة ، فلا يمكنني ببساطة الاستغناء عن MISRA.

إليك ما تمكنت من العثور عليه:

/* find and read symlink file */
static int ext2_read_symlink(struct nas *nas,
                             uint32_t parent_inumber,
                             const char **cp) {
  char namebuf[MAXPATHLEN + 1];

  ....

  *cp = namebuf;              // <=
  if (*namebuf != '/') {
    inumber = parent_inumber;
  } else {
    inumber = (uint32_t) EXT2_ROOTINO;
  }
  rc = ext2_read_inode(nas, inumber);

  return rc;
} 

تحذير PVS-Studio: V2548 [MISRA C 18.6] لا يجب تخزين عنوان الصفيف المحلي "namebuf" خارج نطاق هذا الصفيف. ext2.c 298

اكتشف المحلل مهمة مشبوهة يمكن أن تؤدي إلى سلوك غير محدد.

دعونا نلقي نظرة فاحصة على الكود. هنا ، namebuf عبارة عن مصفوفة تم إنشاؤها في النطاق المحلي للدالة ، ويتم تمرير مؤشر cp إلى الوظيفة بواسطة المؤشر.

وفقًا لصيغة C ، فإن اسم الصفيف هو مؤشر للعنصر الأول في منطقة الذاكرة التي يتم تخزين الصفيف فيها. اتضح أن التعبير * cp = namebuf سوف يعين عنوان الصفيف namebuf إلى المتغير المشار إليه بواسطة cp. نظرًا لأن cp يتم تمريره إلى الدالة من خلال المؤشر ، فإن التغيير في القيمة التي يشير إليها سينعكس في المكان الذي تم استدعاء الوظيفة فيه.

اتضح أنه بعد انتهاء دالة ext2_read_symlink من عملها ، ستشير المعلمة الثالثة إلى المنطقة التي كان يشغلها صفيف namebuf مرة واحدة .

يوجد "لكن" واحد فقط: بما أن namebuf عبارة عن مصفوفة محجوزة على المكدس ، فسيتم حذفها عند إنهاء الوظيفة. وبالتالي ، فإن المؤشر الموجود خارج الدالة سيشير إلى الذاكرة المحررة.

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

وجد المحلل أيضًا خطأً آخر له نفس التحذير:

  • V2548 [MISRA C 18.6] لا يجب تخزين عنوان المتغير المحلي "dst_haddr" خارج نطاق هذا المتغير. net_tx.c 82

الشكل 6

استنتاج


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

أغتنم هذه الفرصة لأنقل تحياتي للمطورين من تولا. سأصدق أنه في سان بطرسبرغ ليس الجو باردًا جدًا الآن :)

هنا تنتهي مقالتي. آمل أن تكون قد استمتعت بقراءته ، ووجدت شيئًا جديدًا لنفسك.

إذا كنت مهتمًا بـ PVS-Studio وترغب في التحقق بشكل مستقل من بعض المشاريع التي تستخدمه ، فقم بتنزيله وتجربته . لن يستغرق هذا أكثر من 15 دقيقة.



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

All Articles