LINUX.ORG.RU

Бинарная совместимость, серия 3

 , , , ,


0

5

Всем здравствуйте.

Разобрался, почему Java 1.3 несовместима с современными Glibc (серия 1, серия 2).

С целью выяснения канонического пути до rt.jar процесс JVM вызывает функцию canonicalize(), определённую в файле canonicalize_md.c и находящуюся в libjava.so. Ниже фрагмент исходников Java 1.4.1 (ещё даже не GPL). Более ранних у меня нет, но между 1.3 и 1.4 конкретно этот код вряд ли менялся:

/*
 * @(#)canonicalize_md.c	1.35 01/12/03
 *
 * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

/*
 * Pathname canonicalization for Unix file systems
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
#include <limits.h>
#include <alloca.h>


/* Note: The comments in this file use the terminology
         defined in the java.io.File class */


/* Check the given name sequence to see if it can be further collapsed.
   Return zero if not, otherwise return the number of names in the sequence. */

static int
collapsible(char *names)
{
    char *p = names;
    int dots = 0, n = 0;

    while (*p) {
	if ((p[0] == '.') && ((p[1] == '\0')
			      || (p[1] == '/')
			      || ((p[1] == '.') && ((p[2] == '\0')
						    || (p[2] == '/'))))) {
	    dots = 1;
	}
	n++;
	while (*p) {
	    if (*p == '/') {
		p++;
		break;
	    }
	    p++;
	}
    }
    return (dots ? n : 0);
}


/* Split the names in the given name sequence,
   replacing slashes with nulls and filling in the given index array */

static void
splitNames(char *names, char **ix)
{
    char *p = names;
    int i = 0;

    while (*p) {
	ix[i++] = p++;
	while (*p) {
	    if (*p == '/') {
		*p++ = '\0';
		break;
	    }
	    p++;
	}
    }
}


/* Join the names in the given name sequence, ignoring names whose index
   entries have been cleared and replacing nulls with slashes as needed */

static void
joinNames(char *names, int nc, char **ix)
{
    int i;
    char *p;

    for (i = 0, p = names; i < nc; i++) {
	if (!ix[i]) continue;
	if (i > 0) {
	    p[-1] = '/';
	}
	if (p == ix[i]) {
	    p += strlen(p) + 1;
	} else {
	    char *q = ix[i];
	    while ((*p++ = *q++));
	}
    }
    *p = '\0';
}


/* Collapse "." and ".." names in the given path wherever possible.
   A "." name may always be eliminated; a ".." name may be eliminated if it
   follows a name that is neither "." nor "..".  This is a syntactic operation
   that performs no filesystem queries, so it should only be used to cleanup
   after invoking the realpath() procedure. */

static void
collapse(char *path)
{
    char *names = (path[0] == '/') ? path + 1 : path; /* Preserve first '/' */
    int nc;
    char **ix;
    int i, j;
    char *p, *q;

    nc = collapsible(names);
    if (nc < 2) return;		/* Nothing to do */
    ix = (char **)alloca(nc * sizeof(char *));
    splitNames(names, ix);

    for (i = 0; i < nc; i++) {
	int dots = 0;

	/* Find next occurrence of "." or ".." */
	do {
	    char *p = ix[i];
	    if (p[0] == '.') {
		if (p[1] == '\0') {
		    dots = 1;
		    break;
		}
		if ((p[1] == '.') && (p[2] == '\0')) {
		    dots = 2;
		    break;
		}
	    }
	    i++;
	} while (i < nc);
	if (i >= nc) break;

	/* At this point i is the index of either a "." or a "..", so take the
	   appropriate action and then continue the outer loop */
	if (dots == 1) {
	    /* Remove this instance of "." */
	    ix[i] = 0;
	}
	else {
	    /* If there is a preceding name, remove both that name and this
	       instance of ".."; otherwise, leave the ".." as is */
	    for (j = i - 1; j >= 0; j--) {
		if (ix[j]) break;
	    }
	    if (j < 0) continue;
	    ix[j] = 0;
	    ix[i] = 0;
	}
	/* i will be incremented at the top of the loop */
    }

    joinNames(names, nc, ix);
}


/* Convert a pathname to canonical form.  The input path is assumed to contain
   no duplicate slashes.  On Solaris we can use realpath() to do most of the
   work, though once that's done we still must collapse any remaining "." and
   ".." names by hand. */

int
canonicalize(char *original, char *resolved, int len)
{
    if (len < PATH_MAX) {
	errno = EINVAL;
	return -1;
    }

    if (strlen(original) > PATH_MAX) {
	errno = ENAMETOOLONG;
	return -1;
    }

    /* First try realpath() on the entire path */
    if (realpath(original, resolved)) {
	/* That worked, so return it */
	collapse(resolved);
	return 0;
    }
    else {
	/* Something's bogus in the original path, so remove names from the end
	   until either some subpath works or we run out of names */
	char *p, *end, *r = NULL;
	char path[PATH_MAX + 1];

	strncpy(path, original, sizeof(path));
	if (path[PATH_MAX] != '\0') {
	    errno = ENAMETOOLONG;
	    return -1;
	}
	end = path + strlen(path);

	for (p = end; p > path;) {

	    /* Skip last element */
	    while ((--p > path) && (*p != '/'));
	    if (p == path) break;

	    /* Try realpath() on this subpath */
	    *p = '\0';
	    r = realpath(path, resolved);
	    *p = (p == end) ? '\0' : '/';

	    if (r != NULL) {
		/* The subpath has a canonical path */
		break;
	    }
	    else if (errno == ENOENT || errno == ENOTDIR || errno == EACCES) {
		/* If the lookup of a particular subpath fails because the file
		   does not exist, because it is of the wrong type, or because
		   access is denied, then remove its last name and try again.
		   Other I/O problems cause an error return. */
		continue;
	    }
	    else {
		return -1;
	    }
	}

	if (r != NULL) {
	    /* Append unresolved subpath to resolved subpath */
	    int rn = strlen(r);
	    if (rn + strlen(p) >= len) {
		/* Buffer overflow */
		errno = ENAMETOOLONG;
		return -1;
	    }
	    if ((rn > 0) && (r[rn - 1] == '/') && (*p == '/')) {
		/* Avoid duplicate slashes */
		p++;
	    }
	    strcpy(r + rn, p);
	    collapse(r);
	    return 0;
	}
	else {
	    /* Nothing resolved, so just return the original path */
	    strcpy(resolved, path);
	    collapse(resolved);
	    return 0;
	}
    }

}

И код прекрасен всем, кроме одного: в 2017-м году в Glibc 2.25 появилась стандартная функция с тем же именем, хоть и другой сигнатурой (eaf5ad0bc4a67bf40999e22db6f583ebc3a806ba):

TS 18661-1 defines canonicalize functions to produce a canonical version of a floating-point representation. This patch implements these functions for glibc.

As with the iscanonical macro, these functions are oriented to the decimal floating-point case, where some values have both canonical and noncanonical representations. However, the functions have a return value that says whether they succeeded in storing a canonical result; thus, they can fail for the case of an invalid representation (while still not making any particular choice from among multiple equally canonical valid representations of the same value). Since no floating-point formats in glibc actually have noncanonical valid representations, a type-generic implementation of these functions can be used that expects iscanonical to return 0 only for invalid representations. Now that iscanonical is used within libm.so, libm_hidden_proto / libm_hidden_def are added for __iscanonicall.

The definition of these functions is intended to correspond to a convertFormat operation to the same floating-point format. Thus, they convert signaling NaNs to quiet NaNs, raising the «invalid» exception. Such a conversion «should» produce «the canonical version of that signaling NaN made quiet».

Удивительным образом JVM не валится с segfault, хотя количество аргументов и различается, но, так или иначе, canonicalize(char*, char*, int) из libjava.so не вызывается уж боле.

Вот стектрейс здорового человека (Glibc 2.24, смотрим на фрейм 0):

#0  0xf79c304a in canonicalize () from /usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/i386/libjava.so
#1  0xf79bec50 in Canonicalize () from /usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/i386/libjava.so
#2  0xf7d05c4b in ClassLoader::get_canonical_path(char *, char *, int) () from /usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/x86_64/client/libjvm.so
#3  0xf7d0506f in ClassLoader::setup_bootstrap_search_path(void) () from /usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/x86_64/client/libjvm.so
#4  0xf7d05cad in classLoader_init(void) () from /usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/x86_64/client/libjvm.so
#5  0xf7d1bad3 in init_globals(void) () from /usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/x86_64/client/libjvm.so
#6  0xf7de14ea in Threads::create_vm(JavaVMInitArgs *) () from /usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/x86_64/client/libjvm.so
#7  0xf7d4b620 in JNI_CreateJavaVM () from /usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/x86_64/client/libjvm.so
#8  0x08049bca in InitializeJVM ()
#9  0x08048fd0 in main ()

А вот стектрейс курильщика (фрейм 1 и далее общие, фрейм 0 другой):

#0  __canonicalize (cx=0x8050d50, x=0xffff948c) at ./s_canonicalize_template.c:24
#1  0xf7fa9c50 in Canonicalize () from /usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/i386/libjava.so
#2  0xf7c8dc4b in ClassLoader::get_canonical_path(char *, char *, int) () from /usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/x86_64/client/libjvm.so
#3  0xf7c8d06f in ClassLoader::setup_bootstrap_search_path(void) () from /usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/x86_64/client/libjvm.so
#4  0xf7c8dcad in classLoader_init(void) () from /usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/x86_64/client/libjvm.so
#5  0xf7ca3ad3 in init_globals(void) () from /usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/x86_64/client/libjvm.so
#6  0xf7d694ea in Threads::create_vm(JavaVMInitArgs *) () from /usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/x86_64/client/libjvm.so
#7  0xf7cd3620 in JNI_CreateJavaVM () from /usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/x86_64/client/libjvm.so
#8  0x08049bca in InitializeJVM ()
#9  0x08048fd0 in main ()

Здесь значения cx и x – видимо, исходные значения указателей на строки путей (char *), интерпретированные как double *.

Собственно, вопрос.

Я решил, что я, типа, умный, и сейчас я возьму, соберу пресловутый сановский исходник в 32-разрядную библиотеку (gcc -m32 ...) и положу её в LD_PRELOAD.

Ага, щаз.

При запуске наблюдаю множественные сообщения вида

ERROR: ld.so: object '.../libcanonicalize.so' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored.

за которыми следует радостный Segmentation fault. Причём, если я явно запускаю 32-разрядный динамический интерпретатор:

LD_PRELOAD='...' /lib/i386-linux-gnu/ld-linux.so.2 java -version

— то результат ровно тот же.

ЧЯДНТ?

★★★★★

Ну и да, в чем смысл пытаться запустить такую древность на современном дистре? Ставьте виртуалку, туда древний debian.

PPP328 ★★★★★
()
Ответ на: комментарий от Syncro

Нет.

Там скрипт криво пути определяет по выводу uname, пришлось добавить симлинки.

64-разрядная версия Java 1.3 была только под SPARCv9.

Bass ★★★★★
() автор топика
Последнее исправление: Bass (всего исправлений: 1)
Ответ на: комментарий от Bass

а может это другой эльф? я когда-то читал/слышал, что эти форматы менялись и не только из-за разрядностей. Возможно ядро как-то отличает старый эльф и может исполнять, но линковать вместе уже не получается. Как вариант, можно достать образ дистрибутива тех лет, может быть даже с теми же глибсами и гцц и в виртуалке собрать костыль-библиотечку в нем.

Syncro ★★★★★
()
Последнее исправление: Syncro (всего исправлений: 1)

Я бы попробовал пропатчить джавовский бинарник чтобы он не конфликтовал с glibc-функцией. Либо просто поменять какой-нить символ в его названии функции, либо вообще сделать её вызов статически (если это возможно).

firkax ★★★★★
()

И код прекрасен всем, кроме одного: в 2017-м году в Glibc 2.25 появилась стандартная функция с тем же именем, хоть и другой сигнатурой (eaf5ad0bc4a67bf40999e22db6f583ebc3a806ba):

Очередная криворукость формата ELF. В PE и Mach-O таких проблем нет потому что символы ищатся по паре <library>object, а не только по object.

X512 ★★★★★
()
Последнее исправление: X512 (всего исправлений: 1)
Ответ на: комментарий от ox55ff

А как ты без хаков реализуешь tcp wrappers, или всякие профайлеры, подменяющие malloc/free и т.п., и чтобы оно при этом не тормозило? Без LD_PRELOAD для таких вещей придётся целую гравицапу строить с патчингом бинарников или чем-то похуже.

annulen ★★★★★
()
Ответ на: комментарий от annulen

Можно попробовать через трассировку (ptrace) – PRoot вроде именно так и работает, также решается проблема с тем, что процесс может выполнить системный вызов напрямую, в обход враппера (например, статически скомпилированные приложения).

PeachBlossoms
()
Ответ на: комментарий от annulen

С другой стороны, если чисто через LD_PRELOAD делать, приложение может обойти подмену, делая системные вызовы напрямую (например, в sudo есть параметр NOEXEC в /etc/sudoers, блокирующий вызов execve через LD_PRELOAD, но это не работает на статических бинарниках). Ну и если execve не заблокирован, нельзя гарантировать, что после передачи управления процесс продолжить дёргать системные вызовы только через врапперы, а не напрямую.

PeachBlossoms
()
Ответ на: комментарий от Unixson

странно, а как тоже самое реализовано в PE и Mach-O?

В PE вообще нет динамической линковки в юниксовом смысле. Приложение должно само загружать функции из DLL, используя местные аналоги dlopen/dlsym. Пользуясь этим, можно вклиниться в процесс «линковки», см. «proxy dll»

Насчёт Mach-O не знаю, думаю что без ядерного кода никак, и надо пользоваться инструментами отладки от производителя системы.

annulen ★★★★★
()
Ответ на: комментарий от annulen

В PE вообще нет динамической линковки в юниксовом смысле. Приложение должно само загружать функции из DLL, используя местные аналоги dlopen/dlsym.

Что за бред? PE поддерживает таблицу импорта (Import Directory Table) с той разницей, что всегда указывается из какой библиотеки импортировать. Всё это работает без аналогов dlopen/dlsym, хотя и они тоже доступны по желанию.

X512 ★★★★★
()
Последнее исправление: X512 (всего исправлений: 1)
Ответ на: комментарий от X512

PE поддерживает таблицу импорта (Import Directory Table) с той разницей, что всегда указывается из какой библиотеки импортировать. Всё это работает без аналогов dlopen/dlsym, хотя и они тоже доступны по желанию.

А для чего тогда для dll генерируются import libraries? Разве не для «ручной» загрузки символов из этой таблицы?

annulen ★★★★★
()
Ответ на: комментарий от annulen

А для чего тогда для dll генерируются import libraries? Разве не для «ручной» загрузки символов из этой таблицы?

Судя по названия, PE (portable executable) не умеет в линковку, а вот ELF (executable and linkable format) могёт!

Ну а если серьезно, я подозреваю, что import libraries частично делают то, чем в Linux занимается ld-linux.so, ведь import tables надо нарезать на заглушки для вызова импортированных функций. Плюс в оффтопике при линковке использутеся COFF.

В общем, имеются фундаментальные различия, но в целом суть одна.

kawaii_neko ★★★★
()
Ответ на: комментарий от kawaii_neko

Ну а если серьезно, я подозреваю, что import libraries частично делают то, чем в Linux занимается ld-linux.so, ведь import tables надо нарезать на заглушки для вызова импортированных функций.

Так это именно то, о чём я и писал. Нет никакого динамического линкера, каждый процесс сам грузит себе библиотеки.

В общем, имеются фундаментальные различия, но в целом суть одна.

Ну как сказать, в одной системе есть динамическая линковка, а в другой она эмулируется с помощью внутрипроцессных костылей.

annulen ★★★★★
()
Ответ на: комментарий от annulen

Нет никакого динамического линкера, каждый процесс сам грузит себе библиотеки.

Что за бред? Есть конечно динамический загрузчик (реализован в ntdll.dll). Он использует import directory и export directory для связывания символов.

X512 ★★★★★
()
Последнее исправление: X512 (всего исправлений: 1)
Ответ на: комментарий от annulen

import library это метаданные для компилятора, нужны для тех случаев, когда имя функции в си-исходнике и в dll-ке отличаются и надо из согласовывать, ну и чтобы сообщить компилятору какие именно функции из dll-ки нужно вообще смотреть. Вообще, можно было обойтись и без них, но с ними чуть больше гибкость компиляции. В бинарник их содержимое никак не попадает.

firkax ★★★★★
()
Ответ на: комментарий от annulen

Не, linker scripts это что-то сложнее. А там просто соответствие, например, что функцию SomeFunction() (включая её c++-декорирование, например) следует искать в mylib.dll под именем «SomeFunctionV5», либо под порядковым номером 10221. Для стандартных dll это неизбежно, потому что почти нигде функции под нативными C-именами не хранятся, и даже просто системная C-функция SomeFunction() компилятором внутренне представляется под именем «_SomeFunction» (с подчёркиванием в начале или ещё как-то в зависимости от соглашений о вызове), а в dll скорее всего будет как минимум без подчёркивания. Вобщем, это адаптер между си-стилем внутреннего именования функций и некоторым «кроссязыковым» в dll-ках.

--wrap можно через это реализовать да.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 2)
Ответ на: комментарий от kawaii_neko

Между линковкой PE и ELF важных отличий только два:

1) у ELF-линкера по умолчанию глобальное пространство имён на весь процесс, соответственно не важно в какой именно библиотеке символ, линкер просмотрит все загруженные (но dlopen умеет опционально грузить .so в приватное пространство, откуда его символы будут видны только через dlsym, и рантайм линкер их там не увидит)

2) для ELF допустимы unresolved символы в любом контексте, главное чтобы при итоговой линковке перед запуском они все нашлись - это весьма удобно для всяких so-плагинов, когда символы из хост-бинарника просто объявляются extern и прилинковываются при загрузке этого so - в винде это штатно нельзя

Оба отличия происходят от того, что elf-линкер просто вторая итерация того же самого линкера, который собирает бинарник из .o и статических библиотек.

В PE же рантайм линкер поиском имён не занимается, он только вписывает адреса. Все символы резолвятся на этапе компиляции бинарника (exe или dll), но резолвятся либо статически, если символ тут же, либо «отложенно» - пишется: мы на этапе компиляции «нашли» (обычно благодаря import library) этот символ в такой-то dll-ке, рантайм линкер должен только подгрузить её и вписать из неё адрес.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax

Попробовал — но увы.

Вызов canonicalize() происходит из Canonicalize() ровно в той же библиотеке (libjava.so). Canonicalize() – это просто обёртка, помеченная как JNIEXPORT:

JNIEXPORT int
Canonicalize(JNIEnv *env, char *orig, char *out, int len)
{
    /* canonicalize an already natived path */
    return canonicalize(orig, out, len);
}

Т.е., ещё раз, весь криминал происходит внутри кода одной библиотеки.

Если посмотреть на код функции-обёртки, то он примитивен:

(gdb) disas Canonicalize

0x13c3c <Canonicalize>          push   ebp
0x13c3d <Canonicalize+1>        mov    ebp,esp
0x13c3f <Canonicalize+3>        mov    eax,DWORD PTR [ebp+0x14]
0x13c42 <Canonicalize+6>        push   eax
0x13c43 <Canonicalize+7>        mov    eax,DWORD PTR [ebp+0x10]
0x13c46 <Canonicalize+10>       push   eax
0x13c47 <Canonicalize+11>       mov    eax,DWORD PTR [ebp+0xc]
0x13c4a <Canonicalize+14>       push   eax
0x13c4b <Canonicalize+15>       call   0x13c4c <Canonicalize+16>
0x13c50 <Canonicalize+20>       add    esp,0xc
0x13c53 <Canonicalize+23>       mov    edx,eax
0x13c55 <Canonicalize+25>       mov    eax,edx
0x13c57 <Canonicalize+27>       jmp    0x13c60 <Canonicalize+36>
0x13c59 <Canonicalize+29>       lea    esi,[esi+eiz*1+0x0]
0x13c60 <Canonicalize+36>       leave
0x13c61 <Canonicalize+37>       ret

(gdb) info symbol 0x13c4c

Canonicalize + 16 in section .text

Переименовывать здесь, казалось бы, нечего.

И, тем не менее, запуск java -version с LD_DEBUG=symbols показывает, что символ canonicalize ищется буквально во всех стандартных библиотеках, прежде чем он будет найден в libjava.so (в нормальном сценарии).

$ LD_DEBUG=symbols java -version 2>&1 | grep '=canonicalize'
      1584:     symbol=canonicalize;  lookup in file=/usr/lib/jvm/java-1.3.1_20-sun-i386/bin/x86_64/native_threads/java [0]
      1584:     symbol=canonicalize;  lookup in file=/lib/i386-linux-gnu/libpthread.so.0 [0]
      1584:     symbol=canonicalize;  lookup in file=/usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/x86_64/native_threads/libhpi.so [0]
      1584:     symbol=canonicalize;  lookup in file=/usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/x86_64/client/libjvm.so [0]
      1584:     symbol=canonicalize;  lookup in file=/lib/i386-linux-gnu/libdl.so.2 [0]
      1584:     symbol=canonicalize;  lookup in file=/lib/i386-linux-gnu/libc.so.6 [0]
      1584:     symbol=canonicalize;  lookup in file=/lib/ld-linux.so.2 [0]
      1584:     symbol=canonicalize;  lookup in file=/lib/i386-linux-gnu/libnsl.so.1 [0]
      1584:     symbol=canonicalize;  lookup in file=/lib/i386-linux-gnu/libm.so.6 [0]
      1584:     symbol=canonicalize;  lookup in file=/usr/lib/i386-linux-gnu/libstdc++-libc6.1-1.so.2 [0]
      1584:     symbol=canonicalize;  lookup in file=/usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/i386/libjava.so [0]
      1584:     symbol=canonicalize;  lookup in file=/usr/lib/jvm/java-1.3.1_20-sun-i386/bin/x86_64/native_threads/java [0]
      1584:     symbol=canonicalize;  lookup in file=/lib/i386-linux-gnu/libpthread.so.0 [0]
      1584:     symbol=canonicalize;  lookup in file=/usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/x86_64/native_threads/libhpi.so [0]
      1584:     symbol=canonicalize;  lookup in file=/usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/x86_64/client/libjvm.so [0]
      1584:     symbol=canonicalize;  lookup in file=/lib/i386-linux-gnu/libdl.so.2 [0]
      1584:     symbol=canonicalize;  lookup in file=/lib/i386-linux-gnu/libc.so.6 [0]
      1584:     symbol=canonicalize;  lookup in file=/lib/ld-linux.so.2 [0]
      1584:     symbol=canonicalize;  lookup in file=/lib/i386-linux-gnu/libnsl.so.1 [0]
      1584:     symbol=canonicalize;  lookup in file=/lib/i386-linux-gnu/libm.so.6 [0]
      1584:     symbol=canonicalize;  lookup in file=/usr/lib/i386-linux-gnu/libstdc++-libc6.1-1.so.2 [0]
      1584:     symbol=canonicalize;  lookup in file=/usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/i386/libjava.so [0]
Bass ★★★★★
() автор топика
Ответ на: комментарий от Bass

Переименовывать здесь, казалось бы, нечего.

Как это нечего,

0x13c4b <Canonicalize+15> call 0x13c4c <Canonicalize+16>

тут явно линкер что-то патчит на запуске. Где именно в бинарнике указано, что тут вместо 0xFFFFFFFC надо подставить правильное смещение функции canonicalize() - я не знаю, но где-то должно быть указано.

А, или это дамп уже слинкованного процесса в памяти? Тогда непонятно, там вызов по битому адресу - это не адрес функции canonicalize() ни из glibc ни из libjava. Но в любом случае где-то это указано.

И переименовать можно, думаю, заметно проще - открыть libjava.so в mcedit-е (или другом двоично-безопасном текстовом редакторе), найти там текстовым поиском все вхождения слова canonicalize и, если их расположение не вызывает каких-то подозрений, заменить там какую-нить букву, например на cbnonicalize.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 2)
Ответ на: комментарий от firkax

Нет, таки без вариантов.

Если переименовать символ canonicalize:

patchelf --output libjava-new.so --rename-dynamic-symbols libjava.map libjava.so

– то при запуске java вижу следующее сообщение:

Unable to load native library: /usr/lib/jvm/java-1.3.1_20-sun-i386/jre/lib/i386/libjava.so: unexpected reloc type 0xe8

Что такое именно unexpected reloc type 0xe8, сходу непонятно, но, скорее всего, libjava.so не является position-независимой (т.е. собрана без -fPIC).

Bass ★★★★★
() автор топика
Ответ на: комментарий от Bass

Не может быть такого чисто от переименования внутреннего символа. Сравни побайтово бинарники, видимо patchelf ещё что-то задел. Или что-то не переименовал например. В результирующем не одного вхождения старого имени не осталось?

firkax ★★★★★
()