Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: STM32 bootloader
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > ARM, 32bit
Страницы: 1, 2, 3
ViKo
А можно ли утоптать загрузчик в память OTP (512 байтов), которая имеется, например, в STM32F207?
AHTOXA
А как туда будет передаваться управление?
ViKo
Цитата(AHTOXA @ Feb 20 2015, 08:46) *
А как туда будет передаваться управление?

Это будет автономный проект, загрузчик. Без прерываний, работа только по последовательному порту. После сброса попадаем в него. Если есть признак, что нужно обновить основную программу (или просто ждать сообщения секунды 4), принимаем, программируем, сбрасываемся. Если признака нет уходим на основную программу. Стартовый адрес (тот, что в прошивке по 0x00000000), сохраняем где-то в недрах..., и используем его для перехода. А вместо него при прошивке пишем адрес загрузчика. И не надо перемещать основную программу, удобно для отладки.

Вижу вероятность получить неработающий прибор, когда сектор 0 уже стер, а записать не успел.
AHTOXA
Цитата(ViKo @ Feb 20 2015, 12:53) *
После сброса попадаем в него.

Я вот про это и спрашивал. Как мы попадаем в него? Насколько я знаю, после сброса можно попасть в три места: в начало флеша, в ОЗУ и в заводской загрузчик.
ViKo
Цитата(AHTOXA @ Feb 20 2015, 12:17) *
Я вот про это и спрашивал. Как мы попадаем в него? Насколько я знаю, после сброса можно попасть в три места: в начало флеша, в ОЗУ и в заводской загрузчик.

Как обычно. В начале flash записан адрес (загрузчика, в данном случае), по которому переходим... ага, эта часть не отображается на нулевые адреса... и из нее, очевидно, нельзя выполнять программы. Но ведь в System Memory скакнуть, вроде, можно?
Ладно, идею похерим. Займу нулевой сектор под загрузчик. Только разобраться надо, как основную программу создавать. Задать ее расположение с адреса 0x0800_4000, что ли?

Я вот чего не пойму. Программный счетчик когда по программе бегает, он какие значения перебирает? Вижу в отладчике 0x0800XXXX. Тогда при чем тут нулевые адреса вообще?
Или в этом случае (работа из flash) адреса 0x00000000 и 0x08000000 - это одно и то же? И можно задать и так, и так? Скажем, вручную в отладчике?
То есть, старшие биты адреса при старте принудительно выставляются в 0x0800 и погнали...
A. Fig Lee
Цитата(ViKo @ Feb 20 2015, 05:36) *
Я вот чего не пойму. Программный счетчик когда по программе бегает, он какие значения перебирает? Вижу в отладчике 0x0800XXXX. Тогда при чем тут нулевые адреса вообще?
Или в этом случае (работа из flash) адреса 0x00000000 и 0x08000000 - это одно и то же? И можно задать и так, и так? Скажем, вручную в отладчике?
То есть, старшие биты адреса при старте принудительно выставляются в 0x0800 и погнали...


Правильные пацаны просто читают мануаль в таком случае.
Угу, одно и тоже. А вот "задать вручную в отладке" это уже методы юных кулхацкеров.
Неизвестно как оно имплементировано, и гарантии нет что везде одинаково и так и будет.
И не факт что поможет.
http://www.keil.com/forum/20219/stm32-and-interrupts/
ViKo
Цитата(A. Fig Lee @ Feb 20 2015, 15:20) *
Правильные пацаны просто читают мануаль в таком случае.
Угу, одно и тоже. А вот "задать вручную в отладке" это уже методы юных кулхацкеров.
Неизвестно как оно имплементировано, и гарантии нет что везде одинаково и так и будет.
И не факт что поможет.

Чему поможет? Мне ничего не надо. А мануалов я начитался тонны. Просто когда не сталкиваешься, не задумываешься. А кое-кто и столкнувшись, не думает. Просто посылает в мануал. rolleyes.gif
Зачем тогда компилировать в адреса 0x08000000? Кидай сразу в 0, и что из RAM, что из Flash будет работать. Зачем мне Keil демонстрирует адреса 0x08000000?

Глянул в отладчике Memory View. Что по 0x00000000, что по 0x08000000 - одинаковое показывает. Первое слово - стек, второе - стартовый адрес - в обоих вариантах 0x0800EAC5. Куда-то в конец скачет, видимо, загрузка переменных.
Сергей Борщ
Видимо затем, что программу еще надо во флешку записать. А когда она заливается через встроенный заводской загрузчик, на нулевые адреса отражен этот самый загрузчик. Поэтому скомпилированная в "родные" адреса 0x0800.... программа будет заливаться любым способом.
ViKo
А в чем тогда вообще великий смысл "отображения"? Ну, сказали бы, когда заданы биты (прочитаны ноги) такие-то, процессор запускается оттуда-то, а когда такие - оттуда. И шабаш, как говорил Шариков. Типа, нет по нулевым адресам ничего, и не будет, не ищите. rolleyes.gif Чтобы не пугать людей страшными цифрами адресов?
A. Fig Lee
Цитата(ViKo @ Feb 20 2015, 08:01) *
Чему поможет? Мне ничего не надо. А мануалов я начитался тонны. Просто когда не сталкиваешься, не задумываешься. А кое-кто и столкнувшись, не думает. Просто посылает в мануал. rolleyes.gif
Зачем тогда компилировать в адреса 0x08000000? Кидай сразу в 0, и что из RAM, что из Flash будет работать. Зачем мне Keil демонстрирует адреса 0x08000000?

А как программа/компилятор поймет это РАМ или флаш, если обращение будет к 0х00000100 ?
ViKo
Цитата(A. Fig Lee @ Feb 20 2015, 17:06) *
А как программа/компилятор поймет это РАМ или флаш, если обращение будет к 0х00000100 ?

А им зачем? Сказано, в 100, значит, в 100. Это пусть процессор мучается, он же знает, как его запустили.
A. Fig Lee
Цитата(ViKo @ Feb 20 2015, 09:08) *
А им зачем? Сказано, в 100, значит, в 100. Это пусть процессор мучается, он же знает, как его запустили.


Ну а процессор что делать будет?
ViKo
Цитата(A. Fig Lee @ Feb 20 2015, 17:16) *
Ну а процессор что делать будет?

Он уже включен железно так, как задано. Может, вам мануал почитать? rolleyes.gif

Мысль, что по нулевым адресам ничего нет, мне нравится больше.
A. Fig Lee
Цитата(ViKo @ Feb 20 2015, 09:33) *
Он уже включен железно так, как задано. Может, вам мануал почитать? rolleyes.gif

Да, не надо нашим людям помогать. Тебя же потом и по кумполу.
Зря влез.
До свидания.
x893
Особенно когда люди даже прочитать не могут, а сразу строчат в форум в надежде, что им разжуют всё (однокашники).
ViKo
Может ли Cortex-M3 выполнять программу из внешней памяти?
x893
Да ему пофиг какой адрес - что указано в 8000004 или 20000004 (от BOOT0/1 зависит) туда и перейдет.
ViKo
Ой ли? Надо по шинам посмотреть в руководстве.
x893
Без Ой-ли. Только hardfault можно получить, если не туда куда можно.
ViKo
Предыдущий оратор путается в показаниях. То у него "без ой-ли", то HardFault.
Есть у кого-нибудь подтверженное практикой мнение, или и так всем ясно (что нельзя)?
SSerge
Цитата(ViKo @ Feb 22 2015, 19:35) *
Есть у кого-нибудь подтверженное практикой мнение, или и так всем ясно (что нельзя)?

Cortex™-M3 Technical Reference Manual от ARM™
утверждает что исполнение кода запрещено только из старших 1/2 Гигабайта адресного пространства (от 0xE0000000 и до упора).
Так что при желании можно поисполнять даже содержимое регистров периферийных устройств.
Т.е. архитектура ARM не запрещает, но конкретно у STM32F1xx не получится, выборка кода запрещена для адресов 0x40000000- 0x5FFFFFFF и от 0xA0000000 до конца памяти.

Под внешнюю память отведен диапазон адресов 0x60000000 - 0x9FFFFFFF, если контроллер внешней памяти настроить соответствующим образом, то можно и из внешней памяти код исполнять.
ViKo
Судя по рисунку, STM32F20X может выполнять программу из внешней памяти. А также и из внутренней OTP Flash, наверное.

А еще под картинкой написано:
S2: S-bus
This bus connects the system bus of the Cortex®-M3 core to a BusMatrix. This bus is used
to access data located in a peripheral or in SRAM. Instructions may also be fetch on this bus
(less efficient than ICode)
. The targets of this bus are the 112 KB & 16 KB internal SRAMs,
the AHB1 peripherals including the APB peripherals, the AHB2 peripherals and the external
memories through the FSMC.

То есть, можно команды выбирать откуда хочешь!? rolleyes.gif
ViKo
Что-то не выходит у меня запустить скомпилированную со сдвигом программу.
Сделал загрузчик, пока только переход на основную программу. Занял нулевой сектор.
В основном проекте задал в Кейле в свойствах проекта начало ПЗУ 0x8004000, размер 0x3C000 (отобрал нулевой сектор для загрузчика), соответственно задал и диапазон для Flash Download. Скомпилировал, зашил. Вижу в ST-Link Utility свое зашитое. Могу и в отладчике в проекте загрузчика посмотреть память. Вроде, все как надо. Но основной проект не работает. Предполагаю, в загрузчике дело. Что-то не так. Вот фрагмент, переход.
Код
/* Если есть SP для приложения */
  if (((*(__IO uint32_t *)APPL_ADDR) & 0x2FFE0000) == 0x20000000) {
/* Адрес таблицы */
    SCB->VTOR = 0x08004000;
    // NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x4000);
/* Указатель на приложение */
    pFunc_t JumpAppl = (pFunc_t)(*(__IO uint32_t *)(APPL_ADDR + 4));
    __set_MSP(*(__IO uint32_t *)APPL_ADDR);
    JumpAppl();
  }
  while (true);
}

SSerge
Цитата(ViKo @ Feb 25 2015, 20:01) *
Но основной проект не работает. Предполагаю, в загрузчике дело. Что-то не так.

NVIC_SetVectorTable() в загрузчике не особо нужен.
Это надо в самой запущенной программе сделать, потому что по умолчанию в SystemInit() регистр SCB->VTOR устанавливается на начало флеша.
ViKo
Цитата(SSerge @ Feb 25 2015, 16:41) *
NVIC_SetVectorTable() в загрузчике не особо нужен.
Это надо в самой запущенной программе сделать, потому что по умолчанию в SystemInit() регистр SCB->VTOR устанавливается на начало флеша.

Это я из примеров выше в теме (или подобных) вытянул. Не использую, такой функции в CMSIS и нет. А SystemInit у меня своя, и ничего с VTOR не делает.
Плохо, в отладчике оба проекта не видны. Дохожу в загрузчике до JumpAppl() и улетаю незнамо куда, в 0xBFFFXXXX.
esaulenka
Цитата(ViKo @ Feb 25 2015, 17:00) *
Дохожу в загрузчике до JumpAppl() и улетаю незнамо куда, в 0xBFFFXXXX.

Кайл умеет ходить шагами по дизассемблеру. Рекомендую, сразу будет видно, ОТКУДА улетает.
ViKo
Цитата(esaulenka @ Feb 25 2015, 18:14) *
Кайл умеет ходить шагами по дизассемблеру. Рекомендую, сразу будет видно, ОТКУДА улетает.

Да вот оттуда и улетает, как JumpAppl() жму. Больше же нет ничего для дизассемблера, проект кончается.
SSerge
Цитата(ViKo @ Feb 25 2015, 21:00) *
Дохожу в загрузчике до JumpAppl() и улетаю незнамо куда, в 0xBFFFXXXX.

Надо смотреть куда линкер таблицу векторов засунул, похоже она осталась на прежнем месте.
Возможно что сегмент кода и сегмент под таблицу векторов настраиваются отдельно.
Сергей Борщ
Цитата(ViKo @ Feb 25 2015, 17:45) *
Да вот оттуда и улетает, как JumpAppl() жму.
Вот прямо в дизассемблере так и написано JumpAppl()? У меня там обычно что-то вроде BL R0 и чуть выше можно увидеть, откуда в R0 вдруг оказался неправильный адрес перехода.
ViKo
Цитата(SSerge @ Feb 25 2015, 20:29) *
Надо смотреть куда линкер таблицу векторов засунул, похоже она осталась на прежнем месте.

Я ее (их обеих) вижу в окнах Memory в отладчике. Все, как надо: адрес стека, адрес по сбросу...

Цитата(Сергей Борщ @ Feb 25 2015, 20:58) *
Вот прямо в дизассемблере так и написано JumpAppl()? У меня там обычно что-то вроде BL R0 и чуть выше можно увидеть, откуда в R0 вдруг оказался неправильный адрес перехода.

У меня тоже, естественно. BL R1. И адрес в нем правильный, из таблицы, что находится по адресу 0x08004004.
А дальше - то, что описал.

Здесь вот какая штука - в основной программе использую Keil RTX RTOS. Может, она и меняет что-то, например, VTOR. Надо посмотреть в несдвинутом проекте.
ViKo
Из программы-загрузчика по стартовому адресу основной программы 0x08004000 переходит. Начинает обнулять переменные и когда обращается к внешнему ОЗУ, впадает в HardFault. Устанавливается бит Imprecise data access violation, и всё.
Если же основную программу скомпилировать с адреса 0x08000000, то работает.

Складывается впечатление, что нужно сконфигурировать ExtMem. Что делается в кейловской SystemInit, вместе с установкой рабочей частоты, еще до обнуления переменных в __main.
Я SystemInit использую свою, контроллер внешней памяти программирую потом. Почему же программа не висла до этого?

Еще вижу в отладчике для сдвинутой программы в панели NVIC VTO=0x08004000 (как и должно быть), и TBLOFF=0x100080 (а это что за хрень?). В несдвинутой программе последнее - по нулям.
ViKo
Перенес инициализацию FSMC (заодно и всего остального) в SystemInit. Как и предполагал, переход из загрузчика заработал. И как только до этого инициализировались переменные (массивы-буферы), ума не приложу.
Вот только просто ждать таймаута для перехода - не по человечески. Можно по кнопке оставаться в загрузчике. Так ее еще передать и принять нужно...
jcxz
Цитата(ViKo @ Feb 24 2015, 16:58) *
Судя по рисунку, STM32F20X может выполнять программу из внешней памяти. А также и из внутренней OTP Flash, наверное.
...
То есть, можно команды выбирать откуда хочешь!? rolleyes.gif

Должен выполнять, но возможно нужно настроить MPU предварительно.
По-крайней мере для LPC1788 в UM указано:
Default memory space permissions for the Cortex-M3 do not allow program
execution from the address range that includes the dynamic memory chip selects.
These permissions can be changed by programming the MPU

В реальности если запустить код в SDRAM, то при шагании JTAG видно, что несколько команд (с десяток) код выполняется нормально, но на некоторой команде вдруг улетает в
Memory Management Fault (MMFSR.IACCVIOL = 1).
Я только что прописал конфигурирование MPU и после этого всё заработало - теперь программа нормально выполняется из SDRAM (LPC1788).
ViKo
Хочу спросить... Что-то не могу после прошивки новой программы перескочить в основную. Если после сброса, и таймер отсчитал 2 с - переходит нормально. За эти 2 с я должен нажать кнопку на панели, тогда дается таймер в 16 с для приема кода прошивки. Так вот, если кода не пришло, тоже не могу перейти в основную программу. Как будто, что-то переинициализировать нужно.

Добавлю информации. После записи во флэш проверяю, что есть новый адрес для стека (т.е., что прошивка записана, и есть куда идти). Этот момент сигнализирую вспышкой светодиода. Дальше перехожу в основную программу.
Сразу после программирования диод не пыхает, т.е., не обнаруживается зашитая прошивка. После сброса - нормально проходит, после 2 с ожидания в загрузчике. Если нажму кнопку в это время, после 16 с светодиод тоже пыхает, но перехода нет. Чудо.
Запросы от USART стираю. Больше никакими прерываниями не пользуюсь.
P.S. Я, собственно, прерываниями от USART не пользуюсь. Проверяю состояние. Постирал pending биты запросов. Еще забыл про таймер. От него тоже стер. Всё то же.
ViKo
А в отладчике после 16 с перескакивает в основную программу и работает. И после обновления прошивки тоже перескакивает. Что-то я пасую.
Похоже, перескакивает он всегда, только работать, как надо, не хочет.

А еще у меня Keil пишет "*** error 34, line 32: undefined identifier" на строку G, main в файле инициализации отладчика. Конкретно, на main. ?
ViKo
Так и не могу разобраться, что же не дает загрузчику перейти в основную программу сразу после ее прошивки. Ну, попринимал я что-то USART-ом, посчитал время таймером, пописал во флэш... и хочу выйти. Чего не хватает? После сброса (тот же загрузчик, но без приема нового кода) - все нормально запускается. Может, барьеры нужны?

Про кэши ART почитал.
Цитата
To limit the time lost due to jumps, it is possible to retain 64 lines of 128 bits in an instruction cache memory.
...
data cache ... feature works like the instruction cache memory, but the retained data size is limited to 8 rows of 128 bits.

Неслабые такие кэши? Но в загрузчике я их не включаю. И prefetch тоже.
Сергей Борщ
Выходите через сброс (я выхожу через сброс по собаке). При вашем подходе потом замучаетесь в приложении приводить в исходное состояние использованную в загрузчике периферию.
ViKo
Все, исправил. Кнопка от панели передается двумя байтами, да еще и нажатое и отпущенное состояния. Лезло ненужное в основную программу.
Сбрасываю флаг USART_SR_RXNE, основная программа перестала улетать в прерывание.

Теперь задача - зашифровать-расшифровать прошивку. Думаю, не задействовать ли вычислитель CRC в мк.
veteq
Есть проблема со входом в бутлоадер STM32L051 (cortex m0+) из тела программы, микроконтроллер сбрасывается. Тот же код только с другими адресами прекрасно работает на STM32L151 и STM32L401 (cortex m3/m4). Подскажите в чем может быть проблема?

CODE


void (*SysMemBootJump)(void);

SysMemBootJump = (void (*)(void)) (*((unsigned int*) 0x1FF00004));

__disable_irq();
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;

__set_MSP(0x20001000);
SysMemBootJump();

ViKo
Цитата(ViKo @ Mar 13 2015, 16:47) *
Теперь задача - зашифровать-расшифровать прошивку.

Попробовал XTEA. Пока только закодировал. rolleyes.gif Не знаю, сколько времени будет декодироваться в микроконтроллере.
Есть одно замечание. Поскольку кодируются блоки из двух слов, то и перемешивание битов идет в пределах этих двух слов. Дальше все начинается сначала. То есть, одинаковые последовательности слов в начале двоичного файла прошивки (пустые обработчики прерываний) после кодирования тоже будут выглядеть одинаково. Что наталкивает взломщика на принцип кодирования.

Сделал и декодирование, все по https://ru.wikipedia.org/wiki/XTEA#cite_note-report-1
Декодируется быстро, практически, мгновенно.
Фактически, имеется 4 32-битовых ключа, и количество итераций тоже можно выбрать на свой вкус.
ViKo
Теперь подвис на том, как размер кода задать в самом коде. Как здесь писали, после векторов прерываний разместить, например. С помощью линкера (Кейл).
Вариант 2 - занести в код при расчете CRC и кодировании, внешней программой. Это можно. Нужно только зарезервировать в исходнике место для числа-размера. Может, прямо в стартап добавить?

upd. "Нашел" дыру в стартапе. Как, пойдет?
Код
__Vectors       DCD     __initial_sp              ; Top of Stack
                DCD     Reset_Handler             ; Reset Handler
                DCD     NMI_Handler               ; NMI Handler
                DCD     HardFault_Handler         ; Hard Fault Handler
                DCD     MemManage_Handler         ; MPU Fault Handler
                DCD     BusFault_Handler          ; Bus Fault Handler
                DCD     UsageFault_Handler        ; Usage Fault Handler
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     SVC_Handler               ; SVCall Handler
                DCD     DebugMon_Handler          ; Debug Monitor Handler
Сергей Борщ
Цитата(ViKo @ Mar 25 2015, 10:37) *
upd. "Нашел" дыру в стартапе. Как, пойдет?
Пойдет, но я бы не экономил. Потом когда-нибудь будете портировать на более продвинутый кортекс у которого это место в таблице занято и будете долго искать причину неработы. Это раз. Второе - будет довольно сложно заставить линкер класть размер в уже занятое векторами место. А сразу за векторами никто ему мешать не будет.
ViKo
Цитата(Сергей Борщ @ Mar 25 2015, 12:52) *
Пойдет, но я бы не экономил. Потом когда-нибудь будете портировать на более продвинутый кортекс у которого это место в таблице занято и будете долго искать причину неработы. Это раз. Второе - будет довольно сложно заставить линкер класть размер в уже занятое векторами место. А сразу за векторами никто ему мешать не будет.

Полистал книжку по Cortex-M4, там тоже дыра. Можно и после векторов прерываний разместить, но и там сдвиг возможен в новых микроконтроллерах, даже с большей вероятностью.
Линкером класть не умею (вернее, вычислять размер не умею). Буду своей программой записывать, туда же CRC, туда же и серийный номер. STM32 ST-Link Utility умеет перезаписывать флэш (ей серийный номер и обновляю).

Э-э, CRC туда поместить нельзя. Иначе при расчете нужно проскакивать ее. Ее лучше в конец приписать, чтобы полный расчет дал 0.
ViKo
Пробую разместить строки в startup.s, чтобы знать точное место. Добавил после определения векторов:
Код
         EXPORT  Serial
         ALIGN 4
Serial DCB  "0000", 0

Вижу в выходном коде свои строки, если к ним нет обращения. Но не видит программа этих переменных из c-файлов, и не компилируется из-за ошибки!
Поможите, чем можете!
AHTOXA
Напишите перед использованием объявление:
extern uint16_t Serial;
ViKo
Цитата(AHTOXA @ Mar 26 2015, 08:30) *
Напишите перед использованием объявление:
extern uint16_t Serial;

Так не пробовал. У меня же строка.
А extern char *Serial; компилируется, но не работает. Потому что не указатель. А extern char[] Serial не компилируется.
Как-то надо создать указатели на строки в ассемблерном файле. Неужели вручную?
Сообразил:
TxBuffer_write((char *)&Serial);
Сергей Борщ
Цитата(ViKo @ Mar 26 2015, 14:36) *
А extern char *Serial; компилируется, но не работает. Потому что не указатель. А extern char[] Serial не компилируется.
" А кто из телепатов не догадался, что там у меня в исходнике и как ругается компилятор - я не виноват".

Цитата(ViKo @ Mar 26 2015, 14:36) *
Сообразил:
TxBuffer_write((char *)&Serial);
Какое-то масло масляное. Как теперь объявлен Serial?
ViKo
Цитата(Сергей Борщ @ Mar 26 2015, 16:09) *
" А кто из телепатов не догадался, что там у меня в исходнике и как ругается компилятор - я не виноват".
Какое-то масло масляное. Как теперь объявлен Serial?

Я, вроде, всё описал выше.
В startup.s:
Код
                EXPORT  Serial
                ALIGN   4
Serial          DCB     "0002",0

В Main.h:
Код
extern const char Serial;

Использую:
Код
  TxBuffer_write((char *)&Serial);
Сергей Борщ
Цитата(ViKo @ Mar 26 2015, 15:14) *
Я, вроде, всё описал выше.
Про main.h не было. И текста ругани компилятора не было. Вот так должно работать:
Код
extern const char Serial[];

  TxBuffer_write(Serial);



Цитата(ViKo @ Mar 26 2015, 15:14) *
Код
extern const char Serial;
А, тогда понятно, почему потребовалось брать адрес и грубой силой приводить его к другому типу.
ViKo
Цитата(Сергей Борщ @ Mar 26 2015, 17:11) *
Про main.h не было. И текста ругани компилятора не было. Вот так должно работать:
[code]extern const char Serial[];

Это я попутал с C#, не туда скобки присобачил. rolleyes.gif Дал маху. Пишу параллельно шифровщик прошивки на компьютере.

Новая загадка всплыла. Серийный номер нужно же сохранить при обновлении прошивки. И в расчете CRC он не должен участвовать. Эх, что-то криво все выходит...
Можно, конечно, в ту самую OTP его прописать.
upd. Но уже был прецедент - переписали номер на корпусе, чтобы солидней выглядело. Меня в известность не поставили. laughing.gif
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.