пятница, 15 сентября 2017 г.

Определить архитектуру CPU в Qt .pro-файле / detect a CPU architecture in Qt .pro-file

Очень часто надо определить архитектуру в .pro-файле Qt-проекта, чтобы прилинковать нужную версию библиотеки. У меня это обычно выбор между архитектурами x86 или x86_64. Нагуглил вот такое работающее решение:

    contains(QT_ARCH, x86_64) {
        LIBS += ../../linux.x86_64/mymodule.a
    } else {
        LIBS += ../../linux.x86/mymodule.a
    }
Очень удобно, и теперь не надо руками возиться с зависимостями от компилятора или создавать какие-то свои скрипты.

среда, 13 сентября 2017 г.

Добавить строковый дефайн в Qt pro-файле / How to add string define in qt pro-file

Добавление числовых определений для препроцессора или строк без пробело и кавычек достаточно тривиально:

DEFINES += "MYDEF=SOME_STR_1234"

А если строка с пробелами? У меня компилятору понравилось вот такое выражение:

DEFINES += "MYDEF=\"SOME  STR  123 \` *\""

То есть можно использовать бэкслеш для  экранирования спецсимволов и кавычек в том числе.

Теперь можно использовать этот дефайн как строку:

#define xstr(a) str(a)
#define str(a) #a
printf("string [%s]", xstr(MYDEF));

вторник, 5 сентября 2017 г.

How to add file as binary to resources in C# assembly / Как добавить бинарный файл в ресурсы

Достаточно банальная задача, но в Visual Studio 2015 она не решается там просто. Я не нашел никаких способов, чтобы сделать это "правильно". Вижуал Студио постоянно считал, что умнее меня и хранил файл как рисунок или файл соответствующего типа (Icon, Bitmap, Image или String). Единственное, что помогло - это руками подправить resx-файл:

<data name="MyBinaryFile" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>..\Resources\MyBinaryFileName.gif;System.Byte[], mscorlib, Version=4.0.0.0,       Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>

Весь фокус в том, чтобы выставить System.Byte[] в качестве типа импортируемого ресурса. Теперь жмем "rebuild project". Теперь этот файл можно получать вот так:

Byte[] bytes = (Byte[]) global::MyAssembly.Properties.Resources.ResourceManager.GetObject("MyBinaryFile");

Или так:

Byte[] bytes = global::MyAssembly.Properties.Resources.MyBinaryFile;




вторник, 1 августа 2017 г.

bash-скрипт для получения информации о зависимостях

При развертывании приложений в различном окружении часто бывает, что непонятно, какие библиотеки нужно добавлять в дистрибутив, а какие уже и так установлены в ОС. Чтобы это автоматически прояснить, я создал простой скрипт, который просто проверяет, все ли библиотеки, от которых зависит данная библиотека, доступны:

#!/bin/bash

# set env.
export LD_LIBRARY_PATH=$PWD

echo " --- Environment variables:"
echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH"

echo " --- Check library dependencies:"
SO_FILES=lib*.so*
LIBS_CNT=0
LIBS_WITH_NOT_FOUND_DEPS_CNT=0

for f in $SO_FILES
do
    LIBS_CNT=$((LIBS_CNT+1))
    LDD_RES=$(ldd $f)
    if [[ $LDD_RES == *"not found"* ]];
    then
        echo "ldd $f [NOT FOUND ONE OR MORE DEPENDENCIES]"
        ldd $f
        LIBS_WITH_NOT_FOUND_DEPS_CNT=$((LIBS_WITH_NOT_FOUND_DEPS_CNT+1))
    else
        echo "ldd $f [OK]"
    fi
done

echo " --- Results:"
echo "LIBS_CNT $LIBS_CNT"
echo "LIBS_WITH_NOT_FOUND_DEPS_CNT $LIBS_WITH_NOT_FOUND_DEPS_CNT"

В результате удачного запуска (все зависимости найдены) будет такое:

[vasya@localhost the_app]$ ./check_so_deps.sh
 --- Environment variables:
LD_LIBRARY_PATH=/home/vasya/the_app
 --- Check library dependencies:
ldd libc-2.12.so [OK]
ldd libc.so [OK]
ldd libc.so.6 [OK]
ldd libexif.so.12 [OK]
ldd libexif.so.12.3.3 [OK]
ldd libgdiplus.dll.so [OK]
ldd libgdiplus.so.0 [OK]
ldd libgdiplus.so.0.0.0 [OK]
ldd libgif.so.4 [OK]
ldd libgif.so.4.1.6 [OK]
ldd libQtCore.so [OK]
ldd libQtCore.so.4 [OK]
ldd libQtCore.so.4.6 [OK]
ldd libQtCore.so.4.6.2 [OK]
ldd libQtGui.so [OK]
ldd libQtGui.so.4 [OK]
ldd libQtGui.so.4.6 [OK]
ldd libQtGui.so.4.6.2 [OK]
ldd libungif.so.4 [OK]
ldd libungif.so.4.1.6 [OK]
ldd libX11.so [OK]
ldd libX11.so.6 [OK]
ldd libX11.so.6.3.0 [OK]
 --- Results:
LIBS_CNT 29
LIBS_WITH_NOT_FOUND_DEPS_CNT 0

понедельник, 31 июля 2017 г.

Linux mono: DllNotFoundException & Dll mapping

Недавно возникла необходимость создать standalone-пекедж с приложением для Linux на mono. Традиционно эта задача решается очень просто: в папку с приложением накидываются все необходимые файлы, пути, где лежат библиотеки, добавляются в LD_LIBRARY_PATH (для обычных *.so-библиотек) и в MONO_PATH (для библиотек mono). Скрипт запуска будет выглядеть так:

export LD_LIBRARY_PATH=$PWD
export MONO_PATH=$PWD
./mono myprog.exe

И вроде бы все прекрасно, но постоянно валятся ошибки типа DllNotFoundException:

System.TypeInitializationException: The type initializer for 'System.Windows.Forms.XplatUI' threw an exception. ---> System.TypeInitializationException: The type initializer for 'System.Drawing.GDIPlus' threw an exception. ---> System.DllNotFoundException: gdiplus.dll

Причем есть два способа решения этой задачи. Первый способ это классический linux-way: просто создаем символическую ссылку с таким названием на нужную библиотеку (в моем случае это libgdiplus.so.0.0.0, например). Но это не очень хороший способ, т.к. нужно угадать с названием, которое может быть и gdiplus.dll.so и libgdiplus.so и еще какое-то. Лучше всего это узнать, включив дебаг:

MONO_LOG_LEVEL=debug  ./mono myprog.exe

и посмотрев, какие библиотеки вызывает mono-фрейворк.

Второй способ, на мой взгляд, лучше: использовать Dll-mapping. Для этого создадим файл System.Drawing.dll.config (поскольку именно библиотека System.Drawing.dll сгенерировала это исключение) следующего содержания (естественно, у вас имя нативного файла может отличаться):
<configuration>
  <dllmap dll="gdiplus.dll" target="libgdiplus.so.0.0.0"/>
</configuration>
Естественно, что для этого надо знать, в какой библиотеке (сборке) находится вызов, который требует ту или иную нативную библиотеку, иначе маппинг не будет работать. В чем преимущество такого способа? Ну например в том, что не надо гадать, какое имя ищет библиотека, когда говорит что не может найти какую-то *.dll (не говоря уже о том, что нативные *.dll-библиотеки под linux уже как-то запутывают, когда знаешь, что они должны быть с расширением *.so). Т.е. мы прямо указываем фреймворку, где искать нативную библиотеку и не пытаемся "угадывать" правильное название библиотеки.

пятница, 5 мая 2017 г.

Install Guest Additions for CentOS-6.5 in OpenBox 5.1

Centos-6.5 уже довольно старая ОС, но в силу некоторых причин пришлось это сделать. Собственно, сама установка гостевой ОС внутри виртуальной машины тривиальна и не требует особых трюков. А вот установка гостевых дополнений - это уже интереснее.
Итак, для начала установим все, что надо для патчинга ядра (здесь и далее все делается из root-консоли, это важно):

yum install kernel-headers kernel-devel

Теперь вставим диск с дополнениями ("Devices" -> "Insert Guest Additions CD image") и идем сюда:

cd /media/VBOXADDITIONS_5.1.22_115126

Пробуем теперь запустить установку гостевых дополнений:

./VBoxLinuxAdditions.run

И мы получаем ошибку. Смотрим, что именно происходит не так:

cat /var/log/vboxadd-install.log

И узнаем, что скрипт не может найти путь к ядру. Ок, давайте сами найдем его:

find / -type d -name '*kernel*'

У нас на выходе будет что-то типа этого:

/selinux/class/kernel_service
/proc/sys/kernel
/etc/kernel
/lib/modules/2.6.32-431.el6.x86_64/kernel
/lib/modules/2.6.32-431.el6.x86_64/kernel/kernel
/lib/modules/2.6.32-431.el6.x86_64/kernel/arch/x86/kernel
/sys/kernel
/sys/module/kernel
/usr/share/selinux/devel/include/kernel
/usr/share/doc/dracut-kernel-004
/usr/share/doc/kernel-firmware-2.6.32
/usr/share/dracut/modules.d/90kernel-modules
/usr/src/kernels
/usr/src/kernels/2.6.32-696.1.1.el6.x86_64/kernel
/usr/src/kernels/2.6.32-696.1.1.el6.x86_64/arch/parisc/kernel
...
/usr/src/kernels/2.6.32-696.1.1.el6.x86_64/arch/microblaze/kernel
/usr/src/kernels/2.6.32-696.1.1.el6.x86_64/arch/score/kernel
/usr/src/kernels/2.6.32-696.1.1.el6.x86_64/arch/m68k/kernel
...
/usr/src/kernels/2.6.32-696.1.1.el6.x86_64/include/config/have/kernel
/usr/src/kernels/2.6.32-696.1.1.el6.x86_64/include/config/kernel
...

Теперь укажем скрипту, где лежит ядро:

export KERN_DIR=/usr/src/kernels/2.6.32-696.1.1.el6.x86_64/

Запускаем скрипт еще раз и опять получаем fail. Смотрим в лог и видим, что теперь скрипт не может найти perl. Хорошо, установим его:

yum install perl

Пробуем еще раз и опять неудача. В этот раз скрипт не смог найти GCC. Установим его тоже:

yum install gcc

Запускам скрипт еще раз и наконец-то все заработало. Перезагружаем виртуальную машину.

Отдельное спасибо Shay Anderson за это руководство.

пятница, 7 апреля 2017 г.

lmutil

Часто платный софт требует привязки к конкретной железке для защиты от копирования. Под linux для этих целей используется lmutil. Я скачал бинарный файл этой тулзы в виде tar.gz файла, распаковал и запустил:

$ ./lmutil lmhostid
bash: ./lmutil: No such file or directory

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

$ sudo apt-get install lsb

Все, теперь запускаем еще раз:

./lmutil lmhostid
lmutil - Copyright (c) 1989-2015 Flexera Software LLC. All Rights Reserved.
The FlexNet host ID of this machine is "000c297b33b0"


"FlexNet host ID" это и есть аппаратный уникальный идентификатор для данного компьютера. Как альтернативу для этого аппаратного идентификатора, можно использовать просто физический адрес сетевого адаптера:

eth0     Link encap:Ethernet  HWaddr 00:0c:29:7b:33:b0 
          inet addr:192.168.145.128  Bcast:192.168.145.255  Mask:255.255.255.0
          inet6 addr: fe80::3d01:cdcf:9364:bfae/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:68466 errors:0 dropped:0 overruns:0 frame:0
          TX packets:5879 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:20744386 (20.7 MB)  TX bytes:467277 (467.2 KB)

lo        Link encap:Local Loopback 
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:80 errors:0 dropped:0 overruns:0 frame:0
          TX packets:80 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1
          RX bytes:11939 (11.9 KB)  TX bytes:11939 (11.9 KB)


Как видим, "аппартаный айди" и физический адрес адаптера это одно и то же в данном случае и в принципе можно было не заморачиваться с установкой этой тулзы.

четверг, 2 марта 2017 г.

Linux command cheatset

A; B    Run A and then B, regardless of success of A
A && B  Run B if A succeeded
A || B  Run B if A failed
A &     Run A in background.
get from here.

понедельник, 9 января 2017 г.

Памятка по компиляции shared libraries

Для начала код разделяемых библиотек (три файла: foo.h, foo1.cpp, foo2.cpp):

// foo.h
 
#ifndef foo_h__
#define foo_h__
 
extern "C"  
__attribute__ ((visibility("default"))) 
void foo(void);
 
#endif  // foo_h__
 
// foo1.cpp

#include <stdio.h>
#include "foo.h"
__attribute__((constructor)) void foo_init() { puts("Init shared library 1"); } __attribute__((destructor)) void foo_deinit() { puts("Deinit shared library 1"); } void foo(void) { puts("Hello, I'm a shared library 1"); }
// foo2.cpp

#include <stdio.h>
#include "foo.h"
__attribute__((constructor))
void foo_init()
{
    puts("Init shared library 2");
}

__attribute__((destructor))
void foo_deinit()
{
    puts("Deinit shared library 2");
}

void foo(void)
{
    puts("Hello, I'm a shared library 2");
}


Теперь код, загружающий библиотеки и использующий их функционал (main.cpp):
#include <stdio.h>
#include <dlfcn.h>
 
//gcc -o main main.cpp -ldl
 
int main() 
{    
    void *h1, *h2;
    // define function prototype
    typedef void (*foo_fn_ptr)(void);     
    // function pointers
    foo_fn_ptr foo1, foo2; 
    char *error;
 
    // open first library
    h1 = dlopen ("./libfoo1.so", RTLD_LAZY);
    if(!h1)
    {
        fputs(dlerror(), stderr);
        puts("");
        return 1;
    }
 
    // get function from first lib
    foo1 = (foo_fn_ptr) dlsym(h1, "foo");
    error = dlerror();
    if (error) 
    {
        puts(error);
        return 1;
    }
 
    // use function
    foo1();
 
    // load second lib
    h2 = dlopen ("./libfoo2.so", RTLD_LAZY);
    if(!h2)
    {
        fputs(dlerror(), stderr);
        puts("");
        return 1;
    }
 
    // get function from second lib
    foo2 = (foo_fn_ptr) dlsym(h2, "foo");
    error = dlerror();
    if (error) 
    {
        puts(error);
        return 1;
    }
 
    // use function
    foo2();
 
    // unload libraries
    dlclose(h1);
    dlclose(h2);
 
    return 0;
}
 
Как все это собирать. Сначала соберем наши shared objects:
gcc -Wall -Werror -o libfoo1.so -shared -fpic foo1.cpp
gcc -Wall -Werror -o libfoo2.so -shared -fpic foo2.cpp

Теперь соберем наш исполняемый файл, который загружает и использует эти библиотеки:
gcc -o main main.cpp -ldl

У нас все собрано и готово, надо только добавить папку, где лежат наши библиотеки в список путей, откуда их можно загружать:
export LD_LIBRARY_PATH=$PWD
Теперь можно запустить:
./main 
Hello world!
Init shared library 1
Hello, I'm a shared library 1
Init shared library 2
Hello, I'm a shared library 2
Deinit shared library 1
Deinit shared library 2
Ниже пояснения.

Код библиотек.
Зачем нам нужен extern "C"? Строго говоря, не нужен, потому что по умолчанию GCC (нынешняя версия, по крайней мере), экспортирует все функции и они доступны для использования, но есть один нюанс. Можно закомментировать эту строчку, пересобрать библиотеку и вместо увидеть ошибку "./libfoo2.so: undefined symbol: foo". То есть функция dlsym не находит символа "foo" в библиотеке, хотя мы точно знаем, что он там есть. С помощью nm мы просмотрим таблицу экспорта нашей библиотеки:
$ nm -D ./libfoo2.so
0000000000201028 B __bss_start
                 w __cxa_finalize
0000000000201028 D _edata
0000000000201030 B _end
00000000000006b4 T _fini
                 w __gmon_start__
0000000000000548 T _init
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 w _Jv_RegisterClasses
                 U puts
0000000000000733 T _Z10foo_deinitv
0000000000000720 T _Z8foo_initv
00000000000006a0 T _Z3foov
$
Вот эта последняя строчка "_Z3foov" и есть имя нашей экспортированной функции и это очевидно не то же самое, что "foo". Теперь раскомментируем строчку extern "C", пересоберем библиотеку и запустим nm еще раз:
$ nm -D ./libfoo2.so
0000000000201028 B __bss_start
                 w __cxa_finalize
0000000000201028 D _edata
0000000000201030 B _end
000000000000075c T _fini
0000000000000746 T foo
                 w __gmon_start__
00000000000005d0 T _init
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 w _Jv_RegisterClasses
                 U puts
0000000000000733 T _Z10foo_deinitv
0000000000000720 T _Z8foo_initv
$ 
Теперь мы видим "foo" и у нас все опять работает:
./main 
Hello world!
Init shared library 1
Hello, I'm a shared library 1
Init shared library 2
Hello, I'm a shared library 2
Deinit shared library 1
Deinit shared library 2

Это все связано с правилами наименования экспортируемых функций, можо погуглить "name mangling", а extern "C" принудительно заставляет компилятор именовать функции по простым и ясным правилам языка C.
Что же насчет __attribute__ ((visibility("default"))), то по умолчанию все функции и так имеют видимость default, но, во избежание всяких неожиданностей типа нестандартных флагов компилятора, мы добавляем этот флаг в каждую экспортируемую функцию.

Сборка библиотек.
Компилируем и создаем из объектного файла shared object двумя отдельными командами.
Компиляция:
gcc -c -Wall -Werror -fpic foo1.cpp
Все нюансы тут состоят в использовании флага pic (Position Independent Code), что необходимо для динамически загружаемых библиотек (статические библиотеки не нуждаются в таком флаге).
Сборка происходит так:
gcc -shared -o libfoo1.so foo1.o
gcc -Wall -Werror -o libfoo1.so -shared -fpic foo1.cpp

тут используется специальный флаг shared для создания динамических библиотек. То же самое одной командой:
gcc -Wall -Werror -o libfoo1.so -shared -fpic foo1.cpp

Сборка main.
Все как обычно, но линкуем библиотеку dl, которая содержит dlopen, dlsym, dlclose и прочие фукции:
gcc -o main main.cpp -ldl
Настройка окружения и запуск.
В linux библиотеки лежат в строго определенных местах и откуда-то еще их загрузить не получится (в отличии от Windows), поэтому, когда мы запустим программу в первый раз, мы увидим что-то такое:
$ ./main 
./libfoo1.so: cannot open shared object file: No such file or directory
$ 
Чтобы добавить новый путь, делаем следующее:
export LD_LIBRARY_PATH=$PWD