Модераторы: xvr
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Проверка ядра FreeBSD. Часть 2, Проверка ядра FreeBSD. Часть 2 
:(
    Опции темы
Alticor
Дата 17.2.2016, 21:40 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 6
Регистрация: 15.12.2015

Репутация: нет
Всего: нет



Заключительная статья про проверку ядра FreeBSD с помощью статического анализатора PVS-Studio


Строки

user posted image

V541 It is dangerous to print the string 'buffer' into itself. ata-highpoint.c 102

Код
static int
ata_highpoint_probe(device_t dev)
{
  ....
  char buffer[64];
  ....
  strcpy(buffer, "HighPoint ");
  strcat(buffer, idx->text);
  if (idx->cfg1 == HPT_374) {
  if (pci_get_function(dev) == 0)
      strcat(buffer, " (channel 0+1)");
  if (pci_get_function(dev) == 1)
      strcat(buffer, " (channel 2+3)");
  }
  sprintf(buffer, "%s %s controller",
    buffer, ata_mode2str(idx->max_dma));
  ....
}


Здесь формируют некую строку в буфере. Потом хотят получить новую строку, сохранив предыдущее значение строки, и добавить к ней ещё два слова. Вроде всё просто.

Для объяснения, почему здесь будет получен неожиданный результат, я процитирую простой и понятный пример из документации к этой диагностике:

Код
char s[100] = "test";
sprintf(s, "N = %d, S = %s", 123, s);


В результате работы этого кода хочется получить строку:

Код

N = 123, S = test



Но на практике в буфере будет сформирована строка:

Код

N = 123, S = N = 123, S =


В других ситуациях аналогичный код может привести не только к выводу некорректного текста, но и к аварийному завершению программы. Код может быть исправлен, если использовать для сохранения результата новый буфер. Корректный вариант:

Код
char s1[100] = "test";
char s2[100];
sprintf(s2, "N = %d, S = %s", 123, s1);


V512 A call of the 'strcpy' function will lead to overflow of the buffer 'p->vendor'. aacraid_cam.c 571

Код
#define  SID_VENDOR_SIZE   8
  char   vendor[SID_VENDOR_SIZE];
#define  SID_PRODUCT_SIZE  16
  char   product[SID_PRODUCT_SIZE];
#define  SID_REVISION_SIZE 4
  char   revision[SID_REVISION_SIZE];

static void
aac_container_special_command(struct cam_sim *sim, union ccb *ccb,
  u_int8_t *cmdp)
{
  ....
  /* OEM Vendor defines */
  strcpy(p->vendor,"Adaptec ");          // <=
  strcpy(p->product,"Array           "); // <=
  strcpy(p->revision,"V1.0");            // <=
  ....
}


Все три строки здесь заполняются неверно. В массивах нет места для null-терминального символа, из-за чего могут возникать серьёзные проблемы при дальнейшей работе с такими строками. В случае с "p->vendor" и "p->product" можно убрать один пробел. Тогда поместится терминальный ноль, который функция strcpy() добавляет в конец строки. А вот для "p->revision" совсем нет места для символа конца строки, поэтому надо увеличить значение SID_REVISION_SIZE хотя бы на единицу.

Мне, конечно, сложно судить об этом коде. Возможно, терминальный ноль и не нужен, и всё рассчитано на определенный размер буфера. Тогда неверно выбрана функция strcpy(). В этом случае следовало написать как-то так:

Код
memcpy(p->vendor,   "Adaptec ",         SID_VENDOR_SIZE);
memcpy(p->product,  "Array           ", SID_PRODUCT_SIZE);
memcpy(p->revision, "V1.0",             SID_REVISION_SIZE);


V583 The '?:' operator, regardless of its conditional expression, always returns one and the same value: td->td_name. subr_turnstile.c 1029

Код
static void
print_thread(struct thread *td, const char *prefix)
{
  db_printf("%s%p (tid %d, pid %d, ....", prefix, td, td->td_tid,
      td->td_proc->p_pid, td->td_name[0] != '\0' ? td->td_name :
      td->td_name);
}


Подозрительное место. Несмотря на проверку "td->td_name[0] != '\0'", эту строку всё равно выводят на печать.

Все такие места:
  • V583 The '?:' operator, regardless of its conditional expression, always returns one and the same value: td->td_name. subr_turnstile.c 1112
  • V583 The '?:' operator, regardless of its conditional expression, always returns one and the same value: td->td_name. subr_turnstile.c 1196


Операции с памятью

В этом разделе я расскажу о неправильном использовании следующих функций:

Код
void bzero(void *b, size_t len);


Функция bzero() заполняет нулями 'len' байт по указателю 'b'.

Код
int copyout(const void *kaddr, void *uaddr, size_t len);


Функция copyout() копирует 'len' байт из 'kaddr' в 'uaddr'.

V579 The bzero function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the second argument. osapi.c 316

Код
/* Autosense storage */  
struct scsi_sense_data sense_data;

void
ostiInitiatorIOCompleted(....)
{
  ....
  bzero(&csio->sense_data, sizeof(&csio->sense_data));
  ....
}


Чтобы обнулить структуру, в функцию bzero() надо передать указатель на структуру и размер обнуляемой памяти в байтах, но тут в функцию передают размер указателя, а не размер структуры.

Правильный вариант должен выглядеть так:

Код
bzero(&csio->sense_data, sizeof(csio->sense_data));


V579 The bzero function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the second argument. acpi_package.c 83

Код
int
acpi_PkgStr(...., void *dst, ....)
{
  ....
  bzero(dst, sizeof(dst));
  ....
}


В этом примере похожая ситуация: в функцию 'bzero' опять передали размер указателя, а не объекта.

Правильный вариант должен выглядеть так:

Код
bzero(dst, sizeof(*dst));


V579 The copyout function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument. if_nxge.c 1498

Код
int
xge_ioctl_stats(xge_lldev_t *lldev, struct ifreq *ifreqp)
{
  ....
  *data = (*data == XGE_SET_BUFFER_MODE_1) ? 'Y':'N';
  if(copyout(data, ifreqp->ifr_data, sizeof(data)) == 0)    // <=
      retValue = 0;
  break;
  ....
}


В данном примере копируют память из 'data' в 'ifreqp->ifr_data', при этом размер копируемой памяти равен sizeof(data), т.е. 4 или 8 байт в зависимости от разрядности архитектуры.


Указатели


user posted image


V557 Array overrun is possible. The '2' index is pointing beyond array bound. if_spppsubr.c 4348

Код
#define AUTHKEYLEN  16

struct sauth {
  u_short  proto;      /* authentication protocol to use */
  u_short  flags;
#define AUTHFLAG_NOCALLOUT  1  
          /* callouts */
#define AUTHFLAG_NORECHALLENGE  2  /* do not re-challenge CHAP */
  u_char  name[AUTHNAMELEN];  /* system identification name */
  u_char  secret[AUTHKEYLEN];  /* secret password */
  u_char  challenge[AUTHKEYLEN];  /* random challenge */
};

static void
sppp_chap_scr(struct sppp *sp)
{
  u_long *ch, seed;
  u_char clen;

  /* Compute random challenge. */
  ch = (u_long *)sp->myauth.challenge;
  read_random(&seed, sizeof seed);
  ch[0] = seed ^ random();
  ch[1] = seed ^ random();
  ch[2] = seed ^ random(); // <=
  ch[3] = seed ^ random(); // <=
  clen = AUTHKEYLEN;
  ....
}


Размер типа 'u_char' - 1 байт в 32-х и 64-х битном приложениях, а размер типа 'u_long' - 4 байта в 32-х битном приложении и 8 байт в 64-х битном приложении. Тогда в 32-х битном приложении при выполнении операции "u_long* ch = (u_long *)sp->myauth.challenge" массив 'ch' будет состоять из 4-х элементов по 4 байта. А в 64-х битном приложении массив 'ch' будет состоять из 2-х элементов по 8 байт. Следовательно, если мы соберём 64-битное ядро, то при обращение к ch[2] и ch[3] происходит выход за границы массива.

V503 This is a nonsensical comparison: pointer >= 0. geom_vinum_plex.c 173

Код
gv_plex_offset(...., int *sdno, int growing)
{
  ....
  *sdno = stripeno % sdcount;
  ....
  KASSERT(sdno >= 0, ("gv_plex_offset: sdno < 0"));
  ....
}


Очень интересное место удалось найти с помощью 503-й диагностики. Нет практического смысла проверять, что значение указателя больше или равно 0. Скорее всего, здесь забыли разыменовать указатель "sdno", чтобы сравнить хранимое там значение.

Ещё два сравнения указателя с нулём:
  • V503 This is a nonsensical comparison: pointer >= 0. geom_vinum_raid5.c 602
  • V503 This is a nonsensical comparison: pointer >= 0. geom_vinum_raid5.c 610

V522 Dereferencing of the null pointer 'sc' might take place. mrsas.c 4027

Код
void
mrsas_aen_handler(struct mrsas_softc *sc)
{
  ....
  if (!sc) {
    device_printf(sc->mrsas_dev, "invalid instance!\n");
    return;
  }
  if (sc->evt_detail_mem) {
  ....
}


Если указатель "sc" нулевой, то выполняется выход из функции. Но тут непонятно, зачем пытаться выполнить разыменование такого указателя "sc->mrsas_dev".

Список странных мест:
  • V522 Dereferencing of the null pointer 'sc' might take place. mrsas.c 1279
  • V522 Dereferencing of the null pointer 'sc' might take place. tws_cam.c 1066
  • V522 Dereferencing of the null pointer 'sc' might take place. blkfront.c 677
  • V522 Dereferencing of the null pointer 'dev_priv' might take place. radeon_cs.c 153
  • V522 Dereferencing of the null pointer 'ha' might take place. ql_isr.c 728

V713 The pointer m was utilized in the logical expression before it was verified against nullptr in the same logical expression. ip_fastfwd.c 245

Код
struct mbuf *
ip_tryforward(struct mbuf *m)
{
  ....
  if (pfil_run_hooks(
      &V_inet_pfil_hook, &m, m->m_pkthdr.rcvif, PFIL_IN, NULL) ||
      m == NULL)
    goto drop;
  ....
}


Проверка "m == NULL" стоит в неправильном месте. Сначала надо выполнить проверку указателя, а только потом только вызывать функцию pfil_run_hooks().


Циклы


user posted image


V621 Consider inspecting the 'for' operator. It's possible that the loop will be executed incorrectly or won't be executed at all. if_ae.c 1663

Код
#define  AE_IDLE_TIMEOUT    100

static void
ae_stop_rxmac(ae_softc_t *sc)
{
  int i;
  ....
  /*
   * Wait for IDLE state.
   */
  for (i = 0; i < AE_IDLE_TIMEOUT; i--) {  // <=
    val = AE_READ_4(sc, AE_IDLE_REG);
    if ((val & (AE_IDLE_RXMAC | AE_IDLE_DMAWRITE)) == 0)
      break;
    DELAY(100);
  }
  ....
}


В исходном коде FreeBSD нашёлся такой интересный и неправильный цикл. Неизвестно зачем, но тут делается декремент счётчика цикла, вместо того, чтобы делать инкремент. Получается, что цикл может выполняться гораздо больше, чем значение AE_IDLE_TIMEOUT, пока не выполнится оператор 'break'.

Если цикл вовремя не будет остановлен, то произойдёт переполнение знаковой переменной 'i'. Переполнение знаковой переменной является ничем иным, как неопределённым поведением программы. Причем это не абстрактная теоретическая опасность, а вполне реальная. Недавно мой коллега писал статью на эту тему: "Undefined behavior ближе, чем вы думаете".

Ещё интересный момент. Точно такая же ошибка была обнаружена мной в коде операционной системы Haiku (см. раздел "Предупреждения #17, #18"). Не знаю, кто у кого позаимствовал файл "if_ae.c", но ошибка явно размножается копированием smile.

V535 The variable 'i' is being used for this loop and for the outer loop. Check lines: 182, 183. mfi_tbolt.c 183

Код
mfi_tbolt_adp_reset(struct mfi_softc *sc)
{
  ....
  for (i=0; i < 10; i++) {
    for (i = 0; i < 10000; i++);
  }
  ....
}


Этот небольшой код скорее всего используется для создания задержки, только суммарно тут выполняется 10000 итераций, а не 10*10000, тогда зачем использовать два цикла?

Я специально привел этот пример, т.к. он является наиболее наглядным, когда использование одной переменной во внешних и вложенных циклах приводит к неожиданным результатам.

V535 The variable 'i' is being used for this loop and for the outer loop. Check lines: 197, 208. linux_vdso.c 208

Код
void
__elfN(linux_vdso_reloc)(struct sysentvec *sv, long vdso_adjust)
{
  ....
  for(i = 0; i < ehdr->e_shnum; i++) {                      // <=
    if (!(shdr[i].sh_flags & SHF_ALLOC))
      continue;
    shdr[i].sh_addr += vdso_adjust;
    if (shdr[i].sh_type != SHT_SYMTAB &&
        shdr[i].sh_type != SHT_DYNSYM)
      continue;

    sym = (Elf_Sym *)((caddr_t)ehdr + shdr[i].sh_offset);
    symcnt = shdr[i].sh_size / sizeof(*sym);

    for(i = 0; i < symcnt; i++, sym++) {                    // <=
      if (sym->st_shndx == SHN_UNDEF ||
          sym->st_shndx == SHN_ABS)
        continue;
      sym->st_value += vdso_adjust;
    }
  }
  ....
}


А это слишком сложный пример, чтобы понять, правильно ли выполняется код. Но по предыдущему примеру можно сделать вывод, что тут возможно тоже выполняется неверное количество итераций.

V547 Expression 'j >= 0' is always true. Unsigned type value is always >= 0. safe.c 1596

Код
static void
safe_mcopy(struct mbuf *srcm, struct mbuf *dstm, u_int offset)
{
  u_int j, dlen, slen;                   // <=
  caddr_t dptr, sptr;

  /*
   * Advance src and dst to offset.
   */
  j = offset;
  while (j >= 0) {                       // <=
    if (srcm->m_len > j)
      break;
    j -= srcm->m_len;                    // <=
    srcm = srcm->m_next;
    if (srcm == NULL)
      return;
  }
  sptr = mtod(srcm, caddr_t) + j;
  slen = srcm->m_len - j;

  j = offset;
  while (j >= 0) {                       // <=
    if (dstm->m_len > j)
      break;
    j -= dstm->m_len;                    // <=
    dstm = dstm->m_next;
    if (dstm == NULL)
      return;
  }
  dptr = mtod(dstm, caddr_t) + j;
  dlen = dstm->m_len - j;
  ....
}


В этой функции присутствуют два опасных цикла. Т.к. переменная 'j' (счётчики циклов) имеет беззнаковый тип, то проверка "j >= 0" всегда истинна и циклы являются "вечными". Другая проблема заключается в том, что из этого счётчика постоянно вычитаются значения, следовательно, если будет попытка преодолеть нулевое значение, то переменная 'j' примет максимальное значение типа.

V711 It is dangerous to create a local variable within a loop with a same name as a variable controlling this loop. powernow.c 733

Код
static int
pn_decode_pst(device_t dev)
{
  ....
  struct pst_header *pst;                                   // <=
  ....
  p = ((uint8_t *) psb) + sizeof(struct psb_header);
  pst = (struct pst_header*) p;

  maxpst = 200;

  do {
    struct pst_header *pst = (struct pst_header*) p;        // <=

    ....

    p += sizeof(struct pst_header) + (2 * pst->numpstates);
  } while (cpuid_is_k7(pst->cpuid) && maxpst--);            // <=
  ....
}


В теле цикла обнаружено объявление переменной, совпадающей с переменной, используемой для контроля цикла. У меня есть подозрение, что из-за создания локального указателя с таким же именем 'pst', значение внешнего указателя с именем 'pst' не изменяется. Возможно, в условии цикла do....while() всегда проверяется одно и тоже значение "pst->cupid". Разработчикам необходимо перепроверить это место и обязательно дать разные имена переменным.


Разное

V569 Truncation of constant value -96. The value range of unsigned char type: [0, 255]. if_rsu.c 1516

Код
struct ieee80211_rx_stats {
  ....
  uint8_t nf;      /* global NF */
  uint8_t rssi;    /* global RSSI */
  ....
};

static void
rsu_event_survey(struct rsu_softc *sc, uint8_t *buf, int len)
{
  ....
  rxs.rssi = le32toh(bss->rssi) / 2;
  rxs.nf = -96;
  ....
}


Очень подозрительно, что беззнаковой переменной "rxs.nf" присваивается отрицательное значение '-96'. В итоге переменная будет иметь значение '160'.

V729 Function body contains the 'done' label that is not used by any 'goto' statements. zfs_acl.c 2023

Код
int
zfs_setacl(znode_t *zp, vsecattr_t *vsecp, ....)
{
  ....
top:
  mutex_enter(&zp->z_acl_lock);
  mutex_enter(&zp->z_lock);
  ....
  if (error == ERESTART) {
    dmu_tx_wait(tx);
    dmu_tx_abort(tx);
    goto top;
  }
  ....
done:                            // <=
  mutex_exit(&zp->z_lock);
  mutex_exit(&zp->z_acl_lock);

  return (error);
}


В коде встречаются функции, которые содержат метки, но при этом вызов оператора 'goto' для этих меток отсутствует. Например, в данном фрагменте кода метка 'top' используется, а вот 'done' нигде не используется. Возможно, переход на метку забыли добавить или со временем удалили, а метку случайно оставили.

V646 Consider inspecting the application's logic. It's possible that 'else' keyword is missing. mac_process.c 352

Код
static void
mac_proc_vm_revoke_recurse(struct thread *td, struct ucred *cred,
    struct vm_map *map)
{
  ....
  if (!mac_mmap_revocation_via_cow) {
    vme->max_protection &= ~VM_PROT_WRITE;
    vme->protection &= ~VM_PROT_WRITE;
  } if ((revokeperms & VM_PROT_READ) == 0)   // <=
    vme->eflags |= MAP_ENTRY_COW |
        MAP_ENTRY_NEEDS_COPY;
  ....
}


Напоследок хочу рассказать про подозрительное форматирование, с которым уже столкнулся в самом начале проверки проекта. Здесь код оформлен таким образом, что отсутствие ключевого слова 'else' выглядит подозрительным.

V705 It is possible that 'else' block was forgotten or commented out, thus altering the program's operation logics. scsi_da.c 3231

Код
static void
dadone(struct cam_periph *periph, union ccb *done_ccb)
{
  ....
  /*
   * If we tried READ CAPACITY(16) and failed,
   * fallback to READ CAPACITY(10).
   */
  if ((state == DA_CCB_PROBE_RC16) &&
    ....
  } else                                                    // <=
  /*
   * Attach to anything that claims to be a
   * direct access or optical disk device,
   * as long as it doesn't return a "Logical
   * unit not supported" (0x25) error.
   */
  if ((have_sense) && (asc != 0x25)                         // <=
    ....
  } else { 
    ....
  }
  ....
}


В этом коде ещё нет ошибки, но когда-нибудь она точно появится. Оставляя такой большой комментарий перед 'else', можно случайно забыть, что где-то там было это ключевое слово, и неосознанно внести ошибочную правку в код в будущем.


Заключение


user posted image


Проект FreeBSD проверялся специальной версией PVS-Studio, которая показала отличный результат! Весь полученный материал невозможно было уместить в одной этой статье. Тем не менее, команда разработчиков FreeBSD получила весь список предупреждений анализатора, на которые стоит обратить внимание.

Предлагаю всем желающим попробовать PVS-Studio на своих проектах. Анализатор работает в среде Windows. Для использования анализатора в разработке проектов для Linux/FreeBSD у нас нет публичной версии. Но мы можем обсудить возможные варианты заключения контракта по адаптации PVS-Studio для ваших проектов и задач.

PM MAIL   Вверх
_zorn_
Дата 19.2.2016, 16:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 995
Регистрация: 21.8.2007

Репутация: нет
Всего: 12



Фряшные, они настолько консервативные... что умирают... а жаль...

PM MAIL   Вверх
feodorv
Дата 20.2.2016, 18:43 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 2214
Регистрация: 30.7.2011

Репутация: 1
Всего: 45



Всегда с удовольствием читаю про найденные Вашим анализатором ошибки и огрехи. И всё же возникают замечания.

Цитата(Alticor @  17.2.2016,  21:40 Найти цитируемый пост)
V579 The bzero function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the second argument. acpi_package.c 83

int
acpi_PkgStr(...., void *dst, ....)
{
  ....
  bzero(dst, sizeof(dst));
  ....
}

В этом примере похожая ситуация: в функцию 'bzero' опять передали размер указателя, а не объекта.

Правильный вариант должен выглядеть так:

bzero(dst, sizeof(*dst));
Это точно правильный вариант??? sizeof(void)???


Цитата(Alticor @  17.2.2016,  21:40 Найти цитируемый пост)
V713 The pointer m was utilized in the logical expression before it was verified against nullptr in the same logical expression. ip_fastfwd.c 245

struct mbuf *
ip_tryforward(struct mbuf *m)
{
  ....
  if (pfil_run_hooks(
      &V_inet_pfil_hook, &m, m->m_pkthdr.rcvif, PFIL_IN, NULL) ||
      m == NULL)
    goto drop;
  ....
}

Проверка "m == NULL" стоит в неправильном месте. Сначала надо выполнить проверку указателя, а только потом только вызывать функцию pfil_run_hooks().
На мой взгляд, поспешный вывод. Обратите внимание на &m при вызове pfil_run_hooks. То есть, быть может, при входе в функцию ip_tryforward переменная m однозначно не NULL, но после вызова функции pfil_run_hooks может стать NULL'ом, и тогда нужно делать goto drop. Честно говоря, нужно покопаться в коде, чтобы понять, есть здесь ошибка или нет.


Цитата(Alticor @  17.2.2016,  21:40 Найти цитируемый пост)
V579 The copyout function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument. if_nxge.c 1498

int
xge_ioctl_stats(xge_lldev_t *lldev, struct ifreq *ifreqp)
{
  ....
  *data = (*data == XGE_SET_BUFFER_MODE_1) ? 'Y':'N';
  if(copyout(data, ifreqp->ifr_data, sizeof(data)) == 0)    // <=
      retValue = 0;
  break;
  ....
}


В данном примере копируют память из 'data' в 'ifreqp->ifr_data', при этом размер копируемой памяти равен sizeof(data), т.е. 4 или 8 байт в зависимости от разрядности архитектуры.
К сожалению, не приведено объявление переменной data, поэтому судить об ошибке сложно (и это если не обращать внимания на то, что *data затирается при вызове copyout).


Это сообщение отредактировал(а) feodorv - 20.2.2016, 18:43


--------------------
Напильник, велосипед, грабли и костыли - основные инструменты программиста...
PM MAIL   Вверх
borisbn
Дата 22.2.2016, 09:10 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 4874
Регистрация: 6.2.2010
Где: Ростов-на-Дону

Репутация: нет
Всего: 135



Цитата(feodorv @  20.2.2016,  18:43 Найти цитируемый пост)
 Обратите внимание на &m при вызове pfil_run_hooks

Там ещё m->m_pkthdr.rcvif есть, так что должно упасть, если m==NULL. Если бы этого не было, то - да, анализатор мог и ошибиться.


--------------------
Женщины отличаются от программистов тем, что у них чары состоят из стрингов
PM MAIL Jabber   Вверх
feodorv
Дата 22.2.2016, 20:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 2214
Регистрация: 30.7.2011

Репутация: 1
Всего: 45



Цитата(borisbn @  22.2.2016,  09:10 Найти цитируемый пост)
Там ещё m->m_pkthdr.rcvif есть
Совершено верно, почему и есть предположение, что
Цитата(feodorv @  20.2.2016,  18:43 Найти цитируемый пост)
при входе в функцию ip_tryforward переменная m однозначно не NULL
.


--------------------
Напильник, велосипед, грабли и костыли - основные инструменты программиста...
PM MAIL   Вверх
Google
  Дата 23.8.2019, 06:08 (ссылка)  





  Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "С/С++: Программирование под Unix/Linux"
xvr
  • Проставьте несколько ключевых слов темы, чтобы её можно было легче найти.
  • Не забывайте пользоваться кнопкой "Код".
  • Вопросы мобильной разработки тут
  • Телепатов на форуме нет! Задавайте чёткий, конкретный и полный вопрос. Указывайте полностью ошибки компилятора и компоновщика.
  • Новое сообщение должно иметь прямое отношение к разделу форума. Флуд, флейм, оффтопик запрещены.
  • Категорически запрещается обсуждение вареза, "кряков", взлома программ и т.д.

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, xvr.

 
 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Программирование под Unix/Linux | Следующая тема »


 




[ Время генерации скрипта: 0.1014 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.