Распаковка данных в формате LZ4 на Ассемблере
Распаковка данных в формате LZ4 на Ассемблере
Алгоритм сжатия LZ4 был разработан Yann Collet в 2011-м году. При небольшом размере упаковщика и распаковщика, LZ4 обладает очень высокой скоростью обработки данных и хорошей степенью компрессии, поэтому используется в большом числе серьезных проектов. На офсайте есть ссылки на реализации этого алгоритма на различных языках программирования, в том числе и вариант на 16-битном Ассемблере от Jim Leonard. Для использования в своих программах я адаптировал его функцию распаковки LZ4.
В конце статьи вы найдете упаковщик, который сжимает данные по алгоритму LZ4. К нему прилагался bat-файл для демонстрации возможностей, меня удивило, что, несмотря на возможность прямой упаковки файлов, в нем сжимаемый файл передается упаковщику в качестве потока данных через STDIN. Выяснилось, что при упаковке файлов в начало упакованных данных добавляется маркер 03 21 4C 18, а при упаковке потока данных маркер меняется на 02 21 4C 18. На сайте с документацией по LZ4 такой маркер заявлен как устаревший формат блоков упакованных данных, который использовался в ранних версиях алгоритма. Но, как ни странно, при тестировании упаковщика на текстовых данных именно этот формат дал наибольшую степень сжатия. Да и на прочих данных результат оказался ничуть не хуже нового формата. Алгоритм распаковки для нового и старого формата отличается, поэтому функция распаковки учитывает этот момент. Соответственно, упакованные данные должны быть в legacy-формате. Более высокое сжатие дает упаковщик LZ4X от Ильи Муравьева, причем маркер в заголовке сжатых данных записывается правильный.
Code (Assembler) : Убрать нумерацию
- ;------------------------------------------------------------
- ; Распаковка данных в формате LZ4
- ;------------------------------------------------------------
- ; Оригинальный код: Jim Leonard, модификация: ManHunter / PCL
- ;------------------------------------------------------------
- ; На входе:
- ; lpCompressed - указатель на упакованные данные
- ; lpOut - указатель на буфер для распакованных данных
- ; На выходе:
- ; EAX = размер распакованных данных
- ;------------------------------------------------------------
- proc lz4_unpack lpCompressed:DWORD, lpOut:DWORD
- pusha
- mov esi,[lpCompressed]
- mov edi,[lpOut]
- lodsd
- cmp eax,184C2102h
- jne .done
- lodsd
- mov ebx,eax
- add ebx,esi
- xor eax,eax
- xor ecx,ecx
- .parsetoken:
- lodsb
- movzx edx,al
- .copyliterals:
- mov ecx,4
- shr al,cl
- call .buildfullcount
- .doliteralcopy:
- rep movsb
- cmp esi,ebx
- jae .done
- .copymatches:
- lodsw
- xchg edx,eax
- and al,0Fh
- call .buildfullcount
- .domatchcopy:
- push esi
- mov esi,edi
- sub esi,edx
- add ecx,4
- rep movsb
- pop esi
- jmp .parsetoken
- .buildfullcount:
- cmp al,0Fh
- xchg ecx,eax
- jne .builddone
- .buildloop:
- lodsb
- add ecx,eax
- cmp al,0FFh
- je .buildloop
- .builddone:
- retn
- .done:
- sub edi,[lpOut]
- mov [esp+28],edi
- popa
- ret
- endp
В заголовке LZ4 хранится информация о размере упакованных данных, но нет никакой информации об исходных данных. Хорошо, когда размер распакованных данных заранее известен или оперативную память под них можно выделить с запасом. Но если объем данных заранее не определен, то перед распаковкой его надо обязательно узнать. Для таких случаев я немного модифицировал функцию распаковки, чтобы она просто возвращала число байт, которые были бы заполнены в памяти после декомпрессии.
Code (Assembler) : Убрать нумерацию
- ;---------------------------------------------------
- ; Получение размера распакованных данных
- ;---------------------------------------------------
- ; На входе:
- ; lpCompressed - указатель на упакованные данные
- ; На выходе:
- ; EAX = размер распакованных данных
- ;---------------------------------------------------
- proc lz4_size lpCompressed:DWORD
- pusha
- mov esi,[lpCompressed]
- xor edi,edi
- lodsd
- cmp eax,184C2102h
- jne .done
- lodsd
- mov ebx,eax
- add ebx,esi
- xor eax,eax
- xor ecx,ecx
- .parsetoken:
- lodsb
- movzx edx,al
- .copyliterals:
- mov ecx,4
- shr al,cl
- call .buildfullcount
- .doliteralcopy:
- add edi,ecx
- add esi,ecx
- cmp esi,ebx
- jae .done
- .copymatches:
- lodsw
- xchg edx,eax
- and al,0Fh
- call .buildfullcount
- .domatchcopy:
- add ecx,4
- add edi,ecx
- jmp .parsetoken
- .buildfullcount:
- cmp al,0Fh
- xchg ecx,eax
- jne .builddone
- .buildloop:
- lodsb
- add ecx,eax
- cmp al,0FFh
- je .buildloop
- .builddone:
- retn
- .done:
- ; EAX = размер распакованных данных
- mov [esp+28], edi
- popa
- ret
- endp
В приложении пример программы с исходным текстом, которая извлекает из памяти иконку, упакованную по алгоритму LZ4, и выводит ее на форму. Там же в архиве оригинал ассемблерной функции распаковки от Jim Leonard, упаковщик LZ4 от Yann Collet с примерами и упаковщик LZ4X от Ильи Муравьева.
В связи с популярностью алгоритма LZ4, он с небольшими модификациями используется в различных программных продуктах. Например, с его помощью упаковываются файлы с закладками браузера Mozilla Firefox. Там вся разница заключается только в заголовке, который идет перед упакованными данными. После 8-байтного маркера-идентификатора (ASCIIZ-строка "mozLz40") в заголовке записан DWORD с размером оригинальных данных, так что ничего дополнительно вычислять не придется. Размер упакованных данных равен общему размеру данных минус 12 байт заголовка. Функция распаковки будет следующей:
Code (Assembler) : Убрать нумерацию
- ;------------------------------------------------------------
- ; Распаковка данных в формате mozlz4 / jsonlz4
- ;------------------------------------------------------------
- ; На входе:
- ; lpCompressed - указатель на упакованные данные
- ; dSize - размер упакованных данных
- ; lpOut - указатель на буфер для распакованных данных
- ; На выходе:
- ; EAX = размер распакованных данных
- ;------------------------------------------------------------
- proc mozlz4_unpack lpCompressed:DWORD, dSize:DWORD, lpOut:DWORD
- pusha
- mov esi,[lpCompressed]
- mov edi,[lpOut]
- lodsd
- cmp eax,'mozL'
- jne .done
- lodsd
- cmp eax,'z40'
- jne .done
- lodsd
- mov ebx,[dSize]
- sub ebx,12
- add ebx,esi
- xor eax,eax
- xor ecx,ecx
- .parsetoken:
- lodsb
- movzx edx,al
- .copyliterals:
- mov ecx,4
- shr al,cl
- call .buildfullcount
- .doliteralcopy:
- rep movsb
- cmp esi,ebx
- jae .done
- .copymatches:
- lodsw
- xchg edx,eax
- and al,0Fh
- call .buildfullcount
- .domatchcopy:
- push esi
- mov esi,edi
- sub esi,edx
- add ecx,4
- rep movsb
- pop esi
- jmp .parsetoken
- .buildfullcount:
- cmp al,0Fh
- xchg ecx,eax
- jne .builddone
- .buildloop:
- lodsb
- add ecx,eax
- cmp al,0FFh
- je .buildloop
- .builddone:
- retn
- .done:
- sub edi,[lpOut]
- mov [esp+28],edi
- popa
- ret
- endp
Алгоритм LZ4 используется даже в Android-приложениях, а точнее при упаковке dll компоновщиком Xamarin. Тут тоже меняется только заголовок, а упакованные данные прекрасно извлекаются по уже знакомому вам методу. Заголовок состоит из 12 байт: 4 байта маркер "XALZ", затем какой-то четырехбайтный индекс и после него DWORD с размером исходных данных. Распаковывается практически так же, как и предыдущий пример.
Code (Assembler) : Убрать нумерацию
- ;------------------------------------------------------------
- ; Распаковка данных в формате Xamarin LZ4
- ;------------------------------------------------------------
- ; На входе:
- ; lpCompressed - указатель на упакованные данные
- ; dSize - размер упакованных данных
- ; lpOut - указатель на буфер для распакованных данных
- ; На выходе:
- ; EAX = размер распакованных данных
- ;------------------------------------------------------------
- proc xamarin_unpack lpCompressed:DWORD, dSize:DWORD, lpOut:DWORD
- pusha
- mov esi,[lpCompressed]
- mov edi,[lpOut]
- lodsd
- cmp eax,'XALZ'
- jne .done
- lodsd
- lodsd
- mov ebx,[dSize]
- sub ebx,12
- add ebx,esi
- xor eax,eax
- xor ecx,ecx
- .parsetoken:
- lodsb
- movzx edx,al
- .copyliterals:
- mov ecx,4
- shr al,cl
- call .buildfullcount
- .doliteralcopy:
- rep movsb
- cmp esi,ebx
- jae .done
- .copymatches:
- lodsw
- xchg edx,eax
- and al,0Fh
- call .buildfullcount
- .domatchcopy:
- push esi
- mov esi,edi
- sub esi,edx
- add ecx,4
- rep movsb
- pop esi
- jmp .parsetoken
- .buildfullcount:
- cmp al,0Fh
- xchg ecx,eax
- jne .builddone
- .buildloop:
- lodsb
- add ecx,eax
- cmp al,0FFh
- je .buildloop
- .builddone:
- retn
- .done:
- sub edi,[lpOut]
- mov [esp+28],edi
- popa
- ret
- endp
Просмотров: 2210 | Комментариев: 12
Метки: Assembler, распаковка
Внимание! Статья опубликована больше года назад, информация могла устареть!
Комментарии
Отзывы посетителей сайта о статье
ManHunter
(20.03.2023 в 15:28):
Добавил пример распаковщика Xamarin
ManHunter
(20.03.2023 в 14:18):
Добавил пример распаковщика mozlz4 / jsonlz4.
ManHunter
(19.03.2023 в 18:59):
В Xamarin тоже ничего сложного, собственный заголовок + упакованные данные
4 byte magic header - 'XALZ'
4 byte индекс
4 byte размер оригинальных данных
дальше идут упакованные данные
4 byte magic header - 'XALZ'
4 byte индекс
4 byte размер оригинальных данных
дальше идут упакованные данные
ManHunter
(19.03.2023 в 18:10):
По поводу мозилловского LZ4 нашлось вот что
https://github.com/avih/dejsonlz4
http://hg.mozilla.org/mozilla-...4/mfbt/lz4.h
http://hg.mozilla.org/mozilla-...4/mfbt/lz4.c
Это действительно самый обычный LZ4, только с модифицированным заголовком.
В обычном LZ4 заголовок
header db 02h,21h,4Ch,18h
dd packed_size
а тут заголовок
header db 6Dh,6Fh,7Ah,4Ch,7Ah,34h,30h,00h
dd original_size
Надо просто узнать размер сжатых данных (размер данных минус размер заголовка), дальше содержимое прекрасно распаковывается по приведенному здесь алгоритму.
https://github.com/avih/dejsonlz4
http://hg.mozilla.org/mozilla-...4/mfbt/lz4.h
http://hg.mozilla.org/mozilla-...4/mfbt/lz4.c
Это действительно самый обычный LZ4, только с модифицированным заголовком.
В обычном LZ4 заголовок
header db 02h,21h,4Ch,18h
dd packed_size
а тут заголовок
header db 6Dh,6Fh,7Ah,4Ch,7Ah,34h,30h,00h
dd original_size
Надо просто узнать размер сжатых данных (размер данных минус размер заголовка), дальше содержимое прекрасно распаковывается по приведенному здесь алгоритму.
g0b
(24.09.2022 в 19:31):
>Да когда это 1.9.2 новомодным стал?
пардон, 1.9.4 - актуальная версия. 2.6.1 где-то на гитхабе в подвале
пардон, 1.9.4 - актуальная версия. 2.6.1 где-то на гитхабе в подвале
ManHunter
(24.09.2022 в 13:12):
Да когда это 1.9.2 новомодным стал? Всегда был в тренде 2.6.1 или, на крайний случай 1.3. Необычайно информативный комментарий.
g0b
(24.09.2022 в 10:22):
Это все здорово, но вот с новомодным 1.9.2 не пашет.
user
(29.06.2021 в 11:06):
--Добавлено--
Ну, после небольших правок пример распаковывает и этот "XALZ" формат.
Респект.
Ну, после небольших правок пример распаковывает и этот "XALZ" формат.
Респект.
user
(29.06.2021 в 01:45):
Есть ещё такой Xamarin XALZ формат,
пакуют им, полдецы, PE-DLL'и для Android'а.
Вроде используется LZ4, но с заголовками непонятно.
Столкнулся с этой пакостью, надо распаковать и запаковать обратно,
пока ещё не решил как
пакуют им, полдецы, PE-DLL'и для Android'а.
Вроде используется LZ4, но с заголовками непонятно.
Столкнулся с этой пакостью, надо распаковать и запаковать обратно,
пока ещё не решил как
котя
(12.03.2021 в 17:07):
Ого, бомбаракета.
Автору - низкий поклон.
Забрал под unix
Автору - низкий поклон.
Забрал под unix
ManHunter
(11.12.2020 в 10:04):
Только если будет пробегать рядом вместе с толковой документацией. Специально искать не планирую.
Так что отрезаешь заголовок и распаковываешь обычным LZ4. Может быть.
Так что отрезаешь заголовок и распаковываешь обычным LZ4. Может быть.
JSONLZ4
(11.12.2020 в 03:57):
А как насчет распаковки мозилловского формата JSONLZ4?
Там вроде LZ4 модифицированный какой-то
Там вроде LZ4 модифицированный какой-то
Добавить комментарий
Заполните форму для добавления комментария