Eu estava recentemente procurando uma maneira de enviar uma imagem com um executável sem se referir à imagem como arquivo externo. Agora você pode argumentar se é uma boa idéia em geral, mas essa é outra história. Uma vez que eu encontrei muito pouca informação sobre o assunto e necessário reunir informações de várias fontes, concluo a tarefa com esta abrangente escrever sobre o assunto. Os mecanismos descritos aqui se aplicam à inclusão de qualquer recurso - não apenas imagens - em um programa c executável. Há várias maneiras de incorporar dados fixos genéricos dentro de um aplicativo. Let039s começar com um exemplo básico que deve ser familiar: Cada aplicativo é apenas uma coleção de segmentos de dados armazenados em um arquivo. Na arquitetura moderna do PC, cada binário de aplicativos consiste em três grandes segmentos de pilha. Dados e Código. Sem entrar em detalhes, os dados fixos - tais como strings de texto constantes residem na parte somente leitura do Segmento de Dados do binário do aplicativo e o compilador-c cria automaticamente uma região de memória para ele. Quando você compila a linha acima com gcc - c - o mytext. o mytext. c e inspeciona o arquivo de objeto com objdump - t mytext. o you039ll encontra a seção de dados no arquivo de objeto. Que basicamente diz: mystring039 é um objeto de texto global (g) somente leitura (O) e pode ser encontrado no deslocamento 0x00 no arquivo de objeto. Para uma descrição detalhada de todos os valores veja man objdump. Agora uma solução seria simplesmente escrever o código em c e ter o compilador cuidar de tudo usando Para codificar os dados de um determinado arquivo, a ferramenta xxd que vem com o editor de texto vim pode ser usada: xxd - i binaryfile outputs C inclui o estilo de arquivo do arquivo binário dado e grava uma definição de matriz estática completa com o nome do arquivo de entrada: Usando esta variante produz código C portátil e apenas works (TM). No lado negativo, requer pelo menos cinco vezes o tamanho dos dados originais (0xNN, para cada byte de dados original) e o código-fonte precisa ser atualizado sempre que os dados originais são alterados. Embora esta abordagem seja útil em alguns casos, podemos fazer melhor do que isso. O vinculador GNU pode ser usado para criar diretamente arquivos de objetos com uma seção personalizada. data diretamente de qualquer arquivo de entrada. As bandeiras mágicas são - r para tornar o arquivo de objeto realocável e - b binário para vincular arquivos com um formato binário incomum. O arquivo de objeto resultante example. o pode ser vinculado a qualquer aplicativo, simplesmente com o próprio gcc. por exemplo. Gcc - o myapp myapp. c example. o. Agora o último link ausente é acessar os dados no example. o do c-code em myapp. c. Dê uma olhada na saída do objeto e compare com a saída acima para o objeto mystring. A parte gt. data ltnamegt deve tocar um sino. Esta seção de dados pode ser referenciada a partir do código C simplesmente usando: e o comprimento pode ser calculado a partir de ou simplesmente fazendo referência ao endereço de binárioexamplejpgsize. O vinculador resolverá a referência externa ao vincular o aplicativo ea aplicação usará os dados exatamente como um blob fixo no código-fonte. Agora, para a parte complicada: O vinculador GNU comporta-se diferentemente dependendo da plataforma e da arquitetura. As implementações interessantes para mim são GNULinux, OSX e mingw (compilação cruzada binários de janelas em um host GNULinux). O mingw cross-compiler se comporta quase exatamente como gnu-ld com uma diferença menor: a seção de dados não inclui o sublinhado principal: binaryexamplejpgstart vs binaryexamplejpgstart. Bem, lá vai um pouco da elegância da solução, mas esse caso é facilmente manipulado com um ifdef. No entanto, o MacOSX é diferente. O ld que é fornecido com X-code vem da versão llvm 2.7svn e não suporta o recurso de formato de entrada - b. Além disso executáveis universais em OSX pode compreender formatos binários para várias arquiteturas com o formato de seção. data sendo diferente para cada arquitetura. O alinhamento para os dados pode ser diferente entre arquiteturas de 32 bits e 64 bits ea endianess também pode diferir. Assim, a criação da seção de dados precisa ser feita durante a compilação em vez da fase de ligação. No recurso OSX ld039s linking binário foi movido para o seu gcc personalizado, e está disponível através 039-sectcreate039 opção: Para criar uma compilação universal para arquiteturas Intel, adicione - arch i386 - arch x8664 acima da linha de comando. Objdump é também uma ferramenta GNU que não está disponível no OSX. Você pode inspecionar a seção de dados usando otool - s DATA examplejpg pathtoexecutable. Ver homem otool para detalhes lá. Devido à natureza dos binários OSX, referenciar a seção de dados no c-code não é possível com um caractere unsigned externo simples. O vinculador não sabe qual arquitetura será usada e não pode fornecer um endereço. O formato bin mach utilizado pelo OSX precisa ser inspecionado em tempo de execução quando a arquitetura é conhecida e mapear os dados relevantes após o início do aplicativo. A Apple fornece uma API para fazer o que é definido no arquivo de cabeçalho mach-ogetsect. h. Se você tiver x-code instalado você pode ler a documentação sobre ele em man getsectbyname. Resolver o secion só pode ser feito em tempo de execução após a seção de dados foi realocada e pode feito chamando getsectbyname (). No entanto, há um truque que você pode usar, para fazer isso implícito. A seção meta-variável é reconhecida pelo compilador gcc no OSX. Ele produz o mesmo resultado como chamar getsectbyname () addr. Curto de ler o código real, informações sobre interners osx linker não é fácil de encontrar. Getsectbyname () realmente abre o arquivo executável e pesquisa a seção de dados relevantes enquanto o aplicativo está sendo executado. Seção pode ou não pode já ser resolvido em tempo de ligação para uma determinada arquitetura 1). Update (Oct 2016 - Obrigado a Eugene Gershnik): Em versões mais recentes do OSXmacOS que executam executáveis com ASLR. A chamada para getsectbyname precisa ser substituída por getsectiondata 2). No entanto, esta API só está disponível a partir do OS 10.7. Longa história curta, pode-se usar uma abstração macro que funciona x-plataforma. Para acessar os dados e tamanho LDVAR () e LDLEN () são definidos, para a definição externa real do símbolo, EXTLD () é usado: Exemplo de uso em um programa C: Como nota final, alguns cuidados devem ser tomados ao escolher O identificador da variável. Ld usará o nome do arquivo para gerar o nome da seção. Se o nome do arquivo incluir caracteres que não são identificadores C válidos, eles serão transformados em sublinhados. por exemplo. Ld - r - b binário - o exemplo. o. Imagesexample. jpg criará uma região binaryimagesexamplejpg. O. Bem como a barra eo ponto são transformados em sublinhados. Isso não é um problema no OSX onde o identificador precisa ser especificado com a opção - sectcreate. No entanto, os identificadores no OSX estão limitados a 16 caracteres. Portanto, para usar acima da abordagem x-platform, o caminho para o nome do arquivo passado para ld deve ser lt16 chars eo mesmo identificador precisa ser especificado no comando de compilação OSX. Um projeto completo que usa essa abordagem para incluir um arquivo de imagem jpeg e um arquivo de texto javascript é harvid. Ele também descreve como usar um Makefile de plataforma x para criar os arquivos de objeto e adicionando os sinalizadores relevantes para o comando OSX gcc. 1) você deve saber mais sobre isso ou ter qualquer ponteiros para a documentação, entre em contato comigoDoes alguém tem uma idéia de como compilar estaticamente qualquer arquivo de recurso para o arquivo executável ou o arquivo de biblioteca compartilhada usando GCC Por exemplo Id como adicionar arquivos de imagem que nunca mudam (E se o fizerem, eu tenho que substituir o arquivo de qualquer maneira) e wouldnt querem-los a mentir em torno do sistema de arquivos. Se isso é possível (e eu acho que é porque o Visual C para Windows pode fazer isso, também), como posso carregar os arquivos que são armazenados no próprio binário O executável analisar-se, encontrar o arquivo e extrair os dados de Talvez haja uma opção para o GCC que eu ainda não vi. Usando os motores de busca didnt realmente cuspir as coisas certas. Eu precisaria disso para trabalhar em bibliotecas compartilhadas e executáveis ELF normais. Qualquer ajuda é apreciada Dá algo como: Para compatibilidade com outro código, você pode usar fmemopen para obter um objeto FILE regular, ou, em alternativa std :: stringstream para fazer um iostream. Std :: stringstream não é ótimo para isso e você pode naturalmente usar um ponteiro em qualquer lugar você pode usar um iterador. Se você está usando isso com automake não se esqueça de configurar BUILTSOURCES adequadamente. A coisa agradável sobre fazê-lo desta maneira é: Você obtem o texto para fora, assim que pode estar no controle de versão e em remendos sensivelmente É portátil e bem definido em cada plataforma respondida Feb 1 11 em 16:04 Update Eu cresci preferir o controle John Ripleys assembly. incbin solução baseada oferece e agora usar uma variante sobre isso. Eu usei objcopy (GNU binutils) para vincular os dados binários de um arquivo foo-data. bin na seção de dados do executável: Isto dá-lhe um arquivo de objeto foo-data. o que você pode vincular em seu executável. A interface C parece algo assim, você pode fazer coisas como: Se sua arquitetura de destino tem restrições especiais para onde os dados constantes e variáveis são armazenados ou se deseja armazenar esses dados no segmento. text para torná-lo encaixar no mesmo tipo de memória Como o código do programa, você pode jogar com os parâmetros objcopy um pouco mais. Você pode incorporar arquivos binários no executável usando o linker ld. Por exemplo, se você tiver arquivo foo. bar, então você pode incorporá-lo no executável adicionando os seguintes comandos para ld Se você estiver invocando ld através de gcc, então você precisará adicionar - Wl Aqui --formatbinary informa o vinculador que o seguinte arquivo é Binário e --formatdefault retorna ao formato de entrada padrão (isso é útil se você especificar outros arquivos de entrada após o foo. bar). Então você pode acessar o conteúdo do seu arquivo de código: Há também um símbolo chamado binaryfoobarsize. Eu acho que é do tipo uintptrt, mas eu não o verifiquei. Recentemente tive a necessidade de incorporar um arquivo em um executável. Desde que eu estou trabalhando na linha de comando com gcc, et al e não com uma ferramenta RAD fantasia que faz tudo acontecer magicamente não era óbvio para mim como fazer isso acontecer. Um pouco de pesquisa na rede encontrou um hack para essencialmente cat-lo para o final do executável e, em seguida, decifrar onde ele foi baseado em um monte de informações que eu não queria saber sobre. Parecia que deveria haver uma maneira melhor. E há, seu objcopy ao salvamento. Objcopy converte arquivos de objeto ou executáveis de um formato para outro. Um dos formatos que entende é binário, que é basicamente qualquer arquivo thats não em um dos outros formatos que entende. Então, você provavelmente previu a idéia: converter o arquivo que queremos incorporar em um arquivo de objeto, então ele pode simplesmente ser ligado com o resto do nosso código. Vamos dizer que temos um nome de arquivo data. txt que queremos incorporar no nosso executável: Para converter isso em um arquivo de objeto que podemos vincular com o nosso programa que acabamos de usar objcopy para produzir um arquivo. O: Isto diz objcopy que a nossa entrada Arquivo está no formato binário, que nosso arquivo de saída deve estar no formato elf32-i386 (arquivos de objeto no x86). A opção --binary-architecture diz ao objcopy que o arquivo de saída deve ser executado em um x86. Isso é necessário para que ld aceite o arquivo para vinculação com outros arquivos para o x86. Alguém poderia pensar que especificando o formato de saída como elf32-i386 iria implicar isso, mas não. Agora que temos um arquivo de objeto, só precisamos incluí-lo quando executamos o linker: Quando executamos o resultado, obtemos a oração para saída: Claro, eu ainda não contei a história inteira, nem mostrei o main. c. Quando objcopy faz a conversão acima, ele adiciona alguns símbolos de vinculador ao arquivo de objeto convertido: Após a vinculação, esses símbolos especificam o início eo fim do arquivo incorporado. Os nomes dos símbolos são formados pelo prefixo binário e acrescentando início ou fim ao nome do arquivo. Se o nome do arquivo contiver quaisquer caracteres que seriam inválidos em um nome de símbolo, eles serão convertidos em sublinhados (por exemplo, data. txt torna-se datatxt). Se você obter nomes não resolvidos ao vincular usando esses símbolos, faça um hexdump - C no arquivo de objeto e olhe para o final do despejo para os nomes que objcopy escolheu. O código para realmente usar o arquivo incorporado agora deve ser razoavelmente óbvio: Uma coisa importante e sutil a ser observada é que os símbolos adicionados ao objeto são variáveis arent. Eles não contêm quaisquer dados, em vez disso, seu endereço é o seu valor. Eu declará-los como tipo char porque é conveniente para este exemplo: os dados incorporados são dados de caracteres. No entanto, você pode declará-los como qualquer coisa, como int se os dados são uma matriz de inteiros, ou como struct foobart se os dados foram qualquer matriz de foo bares. Se os dados incorporados não forem uniformes, então char é provavelmente o mais conveniente: pegue seu endereço e lance o ponteiro para o tipo apropriado à medida que percorrer os dados. Respondida Apr 1 11 at 20: 39X86-64 Codificação de Instrução 1: Quando qualquer prefixo REX é usado, SPL, BPL, SIL e DIL são usados. Caso contrário, sem qualquer prefixo REX AH, CH, DH e BH são utilizados. Prefixos herdados Cada instrução pode ter até quatro prefixos. Às vezes, um prefixo é necessário para a instrução enquanto ele perde seu significado original (isto é, um prefixo obrigatório). Prefixo grupo 1 0xF0: prefixo LOCK 0xF2: prefixo REPNEREPNZ 0xF3: REP ou prefixo REPEREPZ Grupo de prefixo 2 0x2E: Substituição de segmento CS 0x36: Substituição de segmento SS 0x3E: Substituição do segmento DS 0x26: Substituição do segmento do ES 0x64: Substituição do segmento do FS 0x65: Substituição do segmento GS 0x2E: Filial não tomada 0x3E: Filial tomada Grupo de prefixo 3 0x66: Prefixo de substituição do tamanho do operador Grupo de prefixo 4 0x67: prefixo de substituição de tamanho de endereço Quando há dois ou mais prefixos de Um único grupo, o comportamento é indefinido. Alguns processadores ignoram os prefixos subseqüentes do mesmo grupo ou usam apenas o último prefixo especificado para qualquer grupo. Prefixo LOCK Com o prefixo LOCK, certas instruções read-modify-write são executadas atomicamente. O prefixo LOCK só pode ser usado com as seguintes instruções ou ocorre uma exceção de código de operação inválida: ADC, ADD, E, BTC, BTR, BTS, CMPXCHG, CMPXCHG8B, CMPXCHG16B, DEC, INC, NEG, XADD, XCHG e XOR. REPNEREPNZ, REP e REPEREPZ prefixos Os prefixos de repetição fazem com que as instruções de manipulação de string sejam repetidas. O prefixo REP repetirá a instrução associada até CX vezes, diminuindo CX com cada repetição. Ele pode ser usado com as instruções INS, LODS, MOVS, OUTS e STOS. REPE e REPZ são sinônimos e repitam a instrução até CX atingir 0 ou quando ZF é definido como 0. Ele pode ser usado com as instruções CMPS, CMPSB, CMPSD, CMPSW, SCAS, SCASB, SCASD e SCASW. REPNE e REPNZ também são sinônimos e repetem a instrução até que CX atinja 0 ou quando ZF for definido como 1. Ele pode ser usado com as instruções CSPS, SS, DS, SCPS, SCPS, SCASD, SCPS, SCPS, CMPSB, CMPSD, Prefixos de substituição de segmento de ES, FS e GS As substituições de segmento são usadas com instruções que fazem referência à memória não-stack. O segmento padrão é implícito pela instrução, e usando uma substituição específica força o uso do segmento especificado para operandos de memória. Em 64 bits, as substituições de segmentos CS, SS, DS e ES são ignoradas. Branch tomadasnot tomadas prefixos Branch dicas podem ser usadas para diminuir o impacto do ramo misprediction um pouco. O ramo tomado dica é uma dica forte, enquanto o ramo não tomado dica é uma dica fraca. As dicas de ramo só são suportadas pela Intel desde o Pentium 4. Não é conhecido se o uso delas em arquiteturas AMD tem algum efeito (positivo ou negativo). Prefixo de sobreposição de tamanho de operando e tamanho de endereço O tamanho de operando padrão e o tamanho de endereço podem ser substituídos usando esses prefixos. Consulte a tabela a seguir: 1: Certas instruções padrão (ou fixas em) operandos de 64 bits e não precisam do prefixo REX para isso, consulte esta tabela. NASM determina o tamanho do operando observando os campos MODRM. reg ou (para um registro) MODRM. rm. Quando ambos são de 32 bits, o tamanho do operando torna-se 32 bits. Mesmo para 16 bits e 64 bits. Quando eles diferem, um erro ocorre em tempo de compilação. O tamanho do endereço é determinado olhando (para um operando de memória) o campo MODRM. rm ou o SIB. base. SIB. index e deslocamento, nessa ordem. Assim, quando SIB. base utiliza um registo de 16 bits (tal como AX), o tamanho de endereço torna-se de 16 bits. Usando um deslocamento de 32 bits resultará no deslocamento sendo truncado. O conjunto de instruções x86-64 define muitos opcodes e muitas maneiras de codificá-los, dependendo de vários fatores. Opcodes Legacy Os opcodes legacy (e x87) consistem em, nesta ordem: Prefix obrigatório Certas instruções (especialmente as instruções SIMD) requerem um prefixo obrigatório (0x66, 0xF2 ou 0xF3), que se parece com um prefixo de modificador normal. Quando um prefixo obrigatório é necessário, é colocado com os prefixos de modificador antes do prefixo REX (se houver). Prefixo REX O prefixo REX só está disponível no modo longo. Um prefixo REX deve ser codificado quando: usando um tamanho de operando de 64 bits ea instrução não predefinida para o tamanho de operando de 64 bits ou usando um dos registradores estendidos (R8 a R15, XMM8 a XMM15, YMM8 a YMM15, CR8 a CR15 e DR8 a DR15) ou utilizando um dos registos de byte uniforme SPL, BPL, SIL ou DIL. Um prefixo REX não deve ser codificado quando: usando um dos registradores de alto byte AH, CH, BH ou DH. Em todos os outros casos, o prefixo REX é ignorado. O uso de vários prefixos REX é indefinido, embora os processadores parecem usar apenas o último prefixo REX. As instruções que padrão para o tamanho de operando de 64 bits no modo longo são: O opcode pode ter 1, 2 ou 3 bytes de comprimento. Dependendo da seqüência de escape do opcode, um mapa de opcode diferente é selecionado. As sequências de opcode possíveis são: Note que opcodes pode especificar que o campo REG no byte ModRM é fixo em um valor específico. VEXXOP opcodes Um prefixo VEXXOP deve ser codificado quando: a instrução tem apenas o seu opcode VEXXOP e não são usados os opcode legacy ou os registos YMM de 256 bits ou mais de três operandos (por exemplo, operações de fonte não destrutiva) ou XMM de 128 bits Destino, os bits 128-255 do registo YMM correspondente devem ser apagados. Um prefixo VEXXOP não deve ser codificado quando: ao usar registradores de destino XMM de 128 bits, os bits 128-255 do registro YMM correspondente não devem ser alterados. Existem muitas instruções VEX e XOP, todas as quais podem ser codificadas usando o prefixo de escape VEXXOP de três bytes. Os prefixos de escape VEX e XOP usam campos com a seguinte semântica: 1: No modo de compatibilidade protegida, isto é apenas disp32. Mas no modo longo este é RIPdisp32 (para endereços de 64 bits) ou EIPdisp32 (para endereços de 32 bits, ou seja, com prefixo de substituição de tamanho de endereço, veja aqui). 2: Em modo longo, para codificar disp32 como no modo de compatibilidade protegida, use o byte SIB. Endereçamento relativo RIPEIP O endereçamento em x86-64 pode ser relativo ao valor do ponteiro de instrução atual. Isso é indicado com os registradores de ponteiro de instrução RIP (64 bits) e EIP (32 bits), que não estão expostos ao programa e podem não existir fisicamente. O endereçamento RIP-relativo permite que os arquivos de objeto sejam independentes da localização. O byte SIB tem os seguintes campos: Este campo indica o fator de escala de SIB. index, onde s (como usado nas tabelas) é igual a 2 SIB. scale. O registro de índice para usar. Consulte Registros para os valores a serem usados para cada um dos registros. O REX. X, VEX. Campo X pode estender este campo com 1 bit mais significativo para 4 bits total. O registro base a ser usado. Consulte Registros para os valores a serem usados para cada um dos registros. O REX. B, VEX. B pode estender este campo com 1 bit mais significativo para 4 bits total. Endereçamento de 3264 bits O significado do byte SIB durante o endereçamento de 32 ou 64 bits é o seguinte. O campo mod Mods bytes ModRM e o campo índice bytes SIB são utilizados verticalmente, o campo de base bytes SIB eo bit REXVEXXOP. B horizontalmente. O s é o fator de escala. B. Base. X. Index e Mod estão em binário. 1: Nenhum registro de base é codificado. 2: Nenhum registro de índice é codificado. Deslocamento Um valor de deslocamento é um desvio de 1, 2, 4 ou 8 bytes adicionado ao endereço calculado. Quando um deslocamento de 8 bytes é usado, nenhum operando imediato é codificado. O valor de deslocamento, se houver, segue os bytes ModRM e SIB discutidos acima. Quando as tabelas ModRM ou SIB indicam que é necessário um valor disp, ou sem um byte ModRM, o uso de moffset (AMD) ou moffs (Intel) na sintaxe mnemônica da instrução, então os bytes de deslocamento são necessários. Algumas instruções requerem um valor imediato. A instrução (ea coluna operando-tamanho na tabela acima) determinam o comprimento do valor imediato. O imm8 mnemônico (ou tamanho de operando de 8 bits) significa um valor imediato de um byte, imm16 (ou tamanho de operando de 16 bits) significa um valor imediato de dois bytes, imm32 (ou tamanho de operando de 32 bits) um valor de quatro bytes E imm64 (ou operando tamanho 64-bit) um valor de oito bytes. Quando um valor imediato de 8 bytes é codificado, nenhum deslocamento pode ser codificado. Referências Externas
Comments
Post a Comment