Blog. Just Blog

Разбор параметров командной строки

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Наконец-то добрался до полезной практической задачи по корректному разбору параметров командной строки. На языках высокого уровня это делается чуть ли не одной командой, а на Ассемблере как обычно приходится все делать самостоятельно. Решение получилось универсальным, подходит как для консольных, так и для GUI-приложений. Для использования функции ParseCmdLine в сегменте данных надо предварительно определить следующую структуру:
  1. ; Структура для командной строки
  2. struct  CMDLINE
  3.         nCount   dd ?   ; Количество аргументов
  4.         lpArgs   dd ?   ; Указатель на массив адресов строк
  5.         lpArgStr dd ?   ; Указатель на массив строк
  6. ends
Формат структуры: nCount - количество параметров командной строки, в случае успешного вызова функции это значение обязательно будет ненулевым, так как самый первый параметр - полный путь запуска программы, а остальные аргументы из хвоста командной строки будут расположены, начиная со второго элемента массива. lpArgStr - указатель на массив параметров командной строки. Все строки в этот массив записываются последовательно одна за другой в формате ASCIIZ, если строки были не в кавычках, то с начала и конца строки удаляются избыточные пробелы и символы табуляции. lpArgs - указатель на массив адресов разобранных параметров командной строки. Какого-то отдельного признака окончания массива не предусмотрено, количество элементов берется из значения nCount.

Сама функция разбора командной строки у меня получилась такая. Для работы используется память из кучи (Heap), оригинальная командная строка в памяти не модифицируется.
  1. ;------------------------------------------------------------------
  2. ; Функция разбора параметров командной строки (ANSI)
  3. ; Copyright (C) ManHunter / PCL
  4. ; https://www.manhunter.ru
  5. ;
  6. ; Параметры:
  7. ; CmdStr    - указатель на структуру CMDLINE
  8. ; dQuotFlag - сохранять кавычки: TRUE - сохранять, FALSE - убирать
  9. ; На выходе:
  10. ; заполненная структура CMDLINE с параметрами командной строки
  11. ;------------------------------------------------------------------ 
  12. proc ParseCmdLine CmdStr:dword, dQuotFlag:dword
  13.         local _hheap:DWORD         ; Локальные переменные
  14.         local _tmp:DWORD
  15.         local _nCount:DWORD
  16.         local _dAct:DWORD
  17.  
  18.         virtual at 0
  19.         localcmd CMDLINE           ; Локальная проекция структуры
  20.         end virtual
  21.  
  22.         pusha
  23.  
  24.         ; Получить адрес кучи
  25.         invoke  GetProcessHeap
  26.         mov     [_hheap],eax
  27.         mov     [_nCount],0        ; Обнулить счетчик аргументов
  28.         mov     [_dAct],0          ; Обнулить флаг "работаю"
  29.  
  30.         ; Выделить память для сохранения обработанных аргументов
  31.         invoke  HeapAlloc,[_hheap],HEAP_ZERO_MEMORY,1000h
  32.         mov     edi,eax
  33.         mov     [_tmp],eax
  34.  
  35.         ; Получить командную строку
  36.         invoke  GetCommandLine
  37.         mov     esi,eax
  38.  
  39.         xor     ebx,ebx            ; Флажок-признак, что аргумент в кавычках
  40.  
  41. .parse_scan:
  42.         lodsb
  43.         or      al,al              ; Строка закончилась?
  44.         jz      .parse_end
  45.         cmp     al,'"'             ; Найдена кавычка в параметрах?
  46.         jz      .parse_quote
  47.         cmp     ebx,1              ; Уже обрабатывается строка в кавычках
  48.         je      .parse_arg_in_quote
  49.         cmp     al,' '             ; Встретили пробел, записать параметр
  50.         je      .parse_push_arg
  51.         cmp     al,09h             ; Встретили табулятор, записать параметр
  52.         je      .parse_push_arg
  53.  
  54. .parse_arg_in_quote:
  55.         stosb                      ; Записать текущий символ
  56.         mov     [_dAct],1          ; Установить флаг "работаю"
  57.         jmp     .parse_scan
  58.  
  59. .parse_push_arg:
  60.         mov     al,0               ; Окончание строки
  61.         stosb
  62.         inc     [_nCount]
  63.         mov     [_dAct],0          ; Обнулить флаг "работаю"
  64.  
  65. .parse_remove_space:
  66.         lodsb                      ; Пропустить пробелы и табуляторы
  67.         or      al,al
  68.         jz      .parse_end
  69.         cmp     al,'"'
  70.         je      .parse_start_quote
  71.         cmp     al,' '
  72.         je      .parse_remove_space
  73.         cmp     al,09h
  74.         je      .parse_remove_space
  75.  
  76.         dec     esi
  77.         jmp     .parse_scan
  78.  
  79.         ; Обработка кавычек в командной строке
  80. .parse_quote:
  81.         or      ebx,ebx
  82.         jnz     .parse_end_quote
  83. .parse_start_quote:
  84.         ; Если флаг установлен, то записать и кавычки
  85.         cmp     [dQuotFlag],TRUE
  86.         jne     @f
  87.         stosb
  88. @@:
  89.         ; Установить флаг что обрабатывается параметр в кавычках
  90.         mov     ebx,1
  91.         jmp     .parse_scan
  92. .parse_end_quote:
  93.         ; Если флаг установлен, то записать и кавычки
  94.         cmp     [dQuotFlag],TRUE
  95.         jne     @f
  96.         stosb
  97. @@:
  98.         xor     ebx,ebx
  99.         jmp     .parse_push_arg
  100.  
  101. .parse_end:
  102.         ; Окончание параметров
  103.         xor     eax,eax
  104.         stosw
  105.         mov     eax,[_dAct]
  106.         add     [_nCount],eax
  107.  
  108.         mov     ecx,edi
  109.         sub     ecx,[_tmp]
  110.         push    ecx
  111.  
  112.         ; Выделить память
  113.         invoke  HeapAlloc,[_hheap],HEAP_ZERO_MEMORY,ecx
  114.  
  115.         pop     ecx
  116.  
  117.         mov     edi,eax
  118.         mov     esi,[_tmp]
  119.  
  120.         mov     eax,[CmdStr]
  121.         mov     [eax+localcmd.lpArgStr],edi  ; lpArgStr
  122.  
  123.         ; Перенести строку параметров
  124.         rep     movsb
  125.  
  126.         ; Прибраться за собой
  127.         invoke  HeapFree,[_hheap],0,[_tmp]
  128.  
  129.         mov     eax,[CmdStr]
  130.         mov     ebx,[_nCount]
  131.         mov     [eax+localcmd.nCount],ebx    ; nCount
  132.  
  133.         ; Размер = _nCount * 4
  134.         mov     eax,[_nCount]
  135.         shl     eax,2
  136.  
  137.         ; Выделить память
  138.         invoke  HeapAlloc,[_hheap],HEAP_ZERO_MEMORY,eax
  139.         mov     edi,eax
  140.  
  141.         mov     eax,[CmdStr]
  142.         mov     [eax+localcmd.lpArgs],edi    ; lpArgs
  143.  
  144.         ; Заполнить таблицу индексов
  145.         mov     esi,[eax+localcmd.lpArgStr]
  146. .parse_fill_addr:
  147.         invoke  lstrlen,esi
  148.         or      eax,eax
  149.         jz      .parse_exit
  150.         mov     [edi],esi
  151.         add     edi,4
  152.         add     esi,eax
  153.         inc     esi
  154.         jmp     .parse_fill_addr
  155.  
  156. .parse_exit:
  157.         popa
  158.         ret
  159. endp
Юникодная версия этой функции. Ее тоже можно использовать, но ниже по тексту есть более правильный вариант решения задачи:
  1. ;------------------------------------------------------------------
  2. ; Функция разбора параметров командной строки (Unicode)
  3. ; Copyright (C) ManHunter / PCL
  4. ; https://www.manhunter.ru
  5. ;
  6. ; Параметры:
  7. ; CmdStr    - указатель на структуру CMDLINE
  8. ; dQuotFlag - сохранять кавычки: TRUE - сохранять, FALSE - убирать
  9. ; На выходе:
  10. ; заполненная структура CMDLINE с параметрами командной строки
  11. ;------------------------------------------------------------------
  12. proc ParseCmdLine CmdStr:dword, dQuotFlag:dword
  13.         local _hheap:DWORD         ; Локальные переменные
  14.         local _tmp:DWORD
  15.         local _nCount:DWORD
  16.         local _dAct:DWORD
  17.  
  18.         pusha
  19.  
  20.         ; Получить адрес кучи
  21.         invoke  GetProcessHeap
  22.         mov     [_hheap],eax
  23.         mov     [_nCount],0        ; Обнулить счетчик аргументов
  24.         mov     [_dAct],0          ; Обнулить флаг "работаю"
  25.  
  26.         ; Выделить память для сохранения обработанных аргументов
  27.         invoke  HeapAlloc,[_hheap],HEAP_ZERO_MEMORY,1000h
  28.         mov     edi,eax
  29.         mov     [_tmp],eax
  30.  
  31.         ; Получить командную строку
  32.         invoke  GetCommandLine
  33.         mov     esi,eax
  34.  
  35.         xor     ebx,ebx            ; Флажок-признак, что аргумент в кавычках
  36.  
  37. .parse_scan:
  38.         lodsw
  39.         or      al,al              ; Строка закончилась?
  40.         jz      .parse_end
  41.         cmp     ax,'"'             ; Найдена кавычка в параметрах?
  42.         jz      .parse_quote
  43.         cmp     ebx,1              ; Уже обрабатывается строка в кавычках
  44.         je      .parse_arg_in_quote
  45.         cmp     ax,' '             ; Встретили пробел, записать параметр
  46.         je      .parse_push_arg
  47.         cmp     ax,09h             ; Встретили табулятор, записать параметр
  48.         je      .parse_push_arg
  49.  
  50. .parse_arg_in_quote:
  51.         stosw                      ; Записать текущий символ
  52.         mov     [_dAct],1          ; Установить флаг "работаю"
  53.         jmp     .parse_scan
  54.  
  55. .parse_push_arg:
  56.         mov     al,0               ; Окончание строки
  57.         stosw
  58.         inc     [_nCount]
  59.         mov     [_dAct],0          ; Обнулить флаг "работаю"
  60.  
  61. .parse_remove_space:
  62.         lodsw                      ; Пропустить пробелы и табуляторы
  63.         or      ax,ax
  64.         jz      .parse_end
  65.         cmp     ax,'"'
  66.         je      .parse_start_quote
  67.         cmp     ax,' '
  68.         je      .parse_remove_space
  69.         cmp     ax,09h
  70.         je      .parse_remove_space
  71.  
  72.         dec     esi
  73.         dec     esi
  74.         jmp     .parse_scan
  75.  
  76.         ; Обработка кавычек в командной строке
  77. .parse_quote:
  78.         or      ebx,ebx
  79.         jnz     .parse_end_quote
  80. .parse_start_quote:
  81.         ; Если флаг установлен, то записать и кавычки
  82.         cmp     [dQuotFlag],TRUE
  83.         jne     @f
  84.         stosw
  85. @@:
  86.         ; Установить флаг что обрабатывается параметр в кавычках
  87.         mov     ebx,1
  88.         jmp     .parse_scan
  89. .parse_end_quote:
  90.         ; Если флаг установлен, то записать и кавычки
  91.         cmp     [dQuotFlag],TRUE
  92.         jne     @f
  93.         stosw
  94. @@:
  95.         xor     ebx,ebx
  96.         jmp     .parse_push_arg
  97.  
  98. .parse_end:
  99.         ; Окончание параметров
  100.         xor     eax,eax
  101.         stosd
  102.         mov     eax,[_dAct]
  103.         add     [_nCount],eax
  104.  
  105.         mov     ecx,edi
  106.         sub     ecx,[_tmp]
  107.         push    ecx
  108.  
  109.         ; Выделить память
  110.         invoke  HeapAlloc,[_hheap],HEAP_ZERO_MEMORY,ecx
  111.  
  112.         pop     ecx
  113.  
  114.         mov     edi,eax
  115.         mov     esi,[_tmp]
  116.  
  117.         mov     eax,[CmdStr]
  118.         mov     [eax+CMDLINE.lpArgStr],edi  ; lpArgStr
  119.  
  120.         ; Перенести строку параметров
  121.         rep     movsb
  122.  
  123.         ; Прибраться за собой
  124.         invoke  HeapFree,[_hheap],0,[_tmp]
  125.  
  126.         mov     eax,[CmdStr]
  127.         mov     ebx,[_nCount]
  128.         mov     [eax+CMDLINE.nCount],ebx    ; nCount
  129.  
  130.         ; Размер = _nCount * 4
  131.         mov     eax,[_nCount]
  132.         shl     eax,2
  133.  
  134.         ; Выделить память
  135.         invoke  HeapAlloc,[_hheap],HEAP_ZERO_MEMORY,eax
  136.         mov     edi,eax
  137.  
  138.         mov     eax,[CmdStr]
  139.         mov     [eax+CMDLINE.lpArgs],edi    ; lpArgs
  140.  
  141.         ; Заполнить таблицу индексов
  142.         mov     esi,[eax+CMDLINE.lpArgStr]
  143. .parse_fill_addr:
  144.         invoke  lstrlen,esi
  145.         or      eax,eax
  146.         jz      .parse_exit
  147.         mov     [edi],esi
  148.         add     edi,4
  149.         shl     eax,1
  150.         add     esi,eax
  151.         inc     esi
  152.         inc     esi
  153.         jmp     .parse_fill_addr
  154.  
  155. .parse_exit:
  156.         popa
  157.         ret
  158. endp
Параметры вызова функции: CmdStr - указатель на структуру CMDLINE, dQuotFlag - флаг, определяющий будут ли сохранены обрамляющие кавычки в массиве аргументов командной строки, значение флага: TRUE - сохранять кавычки, FALSE - убирать. После вызова функции получаем заполненную структуру CMDLINE. Пример использования:
  1. section '.data' data readable writeable
  2.         ...
  3. struct  CMDLINE
  4.         nCount   dd ?   ; Количество аргументов
  5.         lpArgs   dd ?   ; Указатель на массив адресов строк
  6.         lpArgStr dd ?   ; Указатель на массив строк
  7. ends
  8. myarg   CMDLINE         ; Это наша командная строка
  9.         ...
  10.  
  11. section '.code' code readable executable
  12.         ...
  13.         stdcall ParseCmdLine,myarg,FALSE
  14.         ; Если не заданы параметры в командной строке, то вывести
  15.         ; информацию о ключах программы
  16.         cmp     [myarg.nCount],2
  17.         jb      show_usage
  18.         ...
Для разбора командной строки в юникодных приложениях лучше использовать связку функций GetCommandLineW для получения этой строки и CommandLineToArgvW для ее преобразования в массив аргументов. Код получается очень простой и элегантный. Никаких дополнительных структур тут не требуется. Единственный недостаток в том, что нельзя получить список аргументов в обрамлении кавычек, но это требуется крайне редко, практически никогда.
  1.         invoke  GetCommandLine
  2.         invoke  CommandLineToArgvW,eax,dData
  3.         or      eax,eax
  4.         jz      loc_error
  5.  
  6.         ; EAX -> указатель на массив параметров
  7.         push    eax
  8.         mov     esi,eax
  9.         ; Количество параметров
  10.         mov     ebx,[dData]
  11. @@:
  12.         ; Указатель на следующий параметр
  13.         lodsd
  14.         ...
  15.         ; EAX -> строка параметра
  16.         ...
  17.  
  18.         ; Следующий параметр
  19.         dec     ebx
  20.         or      ebx,ebx
  21.         jnz     @b
  22.  
  23.         ; Прибраться за собой
  24.         pop     eax
  25.         invoke  LocalFree,eax
В приложении примеры программ с исходными текстами, которые обрабатывают параметры командной строки. Функция ParseCmdLine вызывается дважды с разными флагами обработки кавычек, чтобы вы могли увидеть разницу. Для удобства в архиве есть пакетные файлы для запуска примеров сразу с несколькими параметрами командной строки.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Command.Line.Demo.zip (7,043 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 11916 | Комментариев: 16

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
ManHunter (20.02.2023 в 12:40):
Добавил юникодную версию ParseCmdLine, а также разбор параметров через CommandLineToArgvW. Архив обновлен.
Петренко (12.04.2022 в 22:59):
Благодарствую за подсказку! Нужно опробовать :)
ManHunter (12.04.2022 в 22:42):
Дописывание кода не самая тривиальная задача, да и подсунуть командную строку не так просто. Можно попробовать подменить вызов GetCommandLine на mov eax,fake_cmd и где-нибудь в файле впечатать эту фейковую командную строку. Но оптимальным решением будет патч проверки или лоадер.
Петренко (12.04.2022 в 22:20):
Лоадер и bat-ничек - это, конечно, можно, но хотелось бы научиться и такому подходу. Я так понимаю, что он универсальный для всех программ без защиты. Как я это вижу, нужно в свободное место EXE-шника дописать некий код, который будет подсовывать проге заданные параметры, а затем поменять точку входа в PE-заголовке. Осталось понять, какой код туда дописать) Если есть такие примеры, буду благодарен за помощь. Или не примеры, а просто подсказка, по какому адресу/смещению что поменять.
ManHunter (12.04.2022 в 21:49):
Если в программе есть проверка на наличие этого параметра, то конечно можно. Но смысл? Проще сделать лоадер или вообще обычный bat-ничек для запуска с нужными параметрами.
Петренко (12.04.2022 в 21:06):
ManHunter, а можно как-нибудь убедить (пропатчить) программу, чтобы она считала, что её запустили с параметром? Например, запускаем XXX.EXE, а она думает, что её запустили как "XXX.EXE --help".
ManHunter (25.10.2016 в 08:44):
*facepalm*
Завязывай лакать стекломой по утрам, от него, похоже, мозги протухают и глаза слепнут.
Дмитрий (25.10.2016 в 08:31):
Привет!
Ф можно в Демо примере убрать антисирийский лозунг:
Kill Fuck Asad!
SMaSm-94 (17.11.2014 в 10:37):
ManHunter, да уж, не думал, что моя процедура настолько неоптимизированная ... Но спасибо, в будущем постораюсь быть повнимательнее :)
ManHunter (16.11.2014 в 02:11):
Эх, молодежь....

proc get_command_line_argument nArg:DWORD
        mov     eax,[nArg]
        cmp     eax,[myarg.nCount]
        jb      @f
        xor     eax,eax
        ret
@@:
        shl     eax,2
        add     eax,[myarg.lpArgs]
        mov     eax,[eax]
        ret
endp
SMaSm-94 (12.11.2014 в 11:57):
Вот, на досуге написал процедурку для получения параметра командной строки по порядковому номеру.

; Как же без структуры))
cmdln CMDLINE

; Небольшой пример использования:
        ...
        stdcall ParseCmdLine, cmdln, FALSE
        stdcall get_command_line_argument, 1
        or      eax, eax
        jz      .null_argument
        invoke  MessageBox, HWND_DESKTOP, eax, NULL, MB_OK
        ...

; Сама процедура:
proc get_command_line_argument nArg:DWORD
        xor     eax, eax
        push    ebx
        mov     ebx, [cmdln.nCount]
        dec     ebx
        cmp     [nArg], ebx
        ja      @f
        mov     ebx, [nArg]
        shl     ebx, 2
        push    edi
        xor     edi, edi
        push    esi
        mov     esi, dword [cmdln.lpArgs]
        add     esi, ebx
        lodsd
        pop     esi
        pop     edi
    @@:
        pop     ebx
        ret
endp

Возвращаемые значения:
Если EAX = 0, то параметра под данным номером не существует,
Иначе в EAX будет содержаться адрес строки в массиве lpArgStr.
chak_xakep (04.04.2011 в 15:08):
ManHunter, ну тогда я спокоен. Спасибо ;)
ManHunter (04.04.2011 в 15:01):
Конечно.
chak_xakep (04.04.2011 в 15:01):
Да лан, не злись! :) Вопрос такой, освобождается ли память при завершении приложения?
ManHunter (04.04.2011 в 14:06):
Повторяю: тебе лучше жевать, чем говорить. Если ты НЕ понимаешь смысл написанного, то зачем вообще что-то писать? Залезь под отладчиком, посмотри что в этой "утекшей" памяти находится, после этого можешь указывать мне на "ошибки".
chak_xakep (04.04.2011 в 14:00):
Ахтунг! утечка памяти :) В 127 строке прибрались за собой (HeapFree), а в 138 строчке выделили и забыли освободить. Хотя не так уж и страшно зависит от кол-ва вызовов этой самой ParseCmdLine.

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.0055 сек.) / Память: 4.5 Mb
Наверх