Как добавление вызова функции может привести к тому, что другие символы станут неопределенными при связывании?

Я надеюсь, что кто-то сможет помочь устранить проблему, которая, по моему мнению, является проблемой сценария компоновщика.

У меня возникла странная проблема после добавления вызова новой функции. Без вызова функции мои объектные файлы связываются правильно, однако с добавленным вызовом новой функции я получаю неопределенную ссылку на символ из другого объектного файла (я подтвердил, что он действительно присутствует, используя objdump).

Также странно, что с присутствующим вызовом функции, если я сначала свяжу все объектные файлы с помощью ld -r (чтобы получить перемещаемый вывод), а затем используя свой скрипт ссылки, нет неопределенных ссылок, но кажется, что скрипт ссылки игнорируется, так как выходной двоичный файл не имеет правильной точки входа.

Моя (кросс-компиляторная) версия ld:

> i586-elf-ld - версия
GNU ld (GNU Binutils) 2.20.1.20100303

Мои попытки доказать наличие «отсутствующего» символа:

> i586-elf-ld -T link.ld -o kernel32.bin kernel_loader.o main.o stdio.o common.o gdt.o gdt.bin -y putch

main.o: reference to putch  
stdio.o: definition of putch  
main.o: In function `main':  
main.c:(.text+0x1f): undefined reference to `putch'

N.B. (когда я создавал этот вывод, я использовал имя файла gdt.bin для скомпилированного ассемблера nasm, на самом деле это просто еще один файл .o)

Я вижу символ, который «отсутствует» в соответствующем объектном файле:

> i586-elf-objdump -ht stdio.o
stdio.o: формат файла elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         000002f9  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         0000000c  00000000  00000000  00000330  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000008  00000000  00000000  0000033c  2**2
                  ALLOC
  3 .comment      00000012  00000000  00000000  0000033c  2**0
                  CONTENTS, READONLY
SYMBOL TABLE:
00000000 l    df *ABS*  00000000 stdio.c
00000000 l    d  .text  00000000 .text
00000000 l    d  .data  00000000 .data
00000000 l    d  .bss   00000000 .bss
00000000 l    d  .comment       00000000 .comment
00000000 g     F .text  00000016 strlen
00000016 g     F .text  0000005c scroll
00000008 g     O .data  00000004 numrows
00000004 g     O .bss   00000004 ypos
00000004 g     O .data  00000004 numcols
00000004       O *COM*  00000004 screen_mem
00000000         *UND*  00000000 memcpy
00000000         *UND*  00000000 memsetw
00000072 g     F .text  0000007d newline
00000000 g     O .bss   00000004 xpos
000000ef g     F .text  0000002e writech
00000000 g     O .data  00000004 colour
0000011d g     F .text  00000061 cls
0000017e g     F .text  00000010 init_video
0000018e g     F .text  00000133 putch
000002c1 g     F .text  00000037 puts
000002f8 g     F .text  00000001 set_text_colour

И объектный файл с неразрешенной ссылкой:

> i586-эльф-objdump -ht main.o

main.o:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0000007f  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  00000000  00000000  000000b4  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  000000b4  2**2
                  ALLOC
  3 .rodata.str1.1 00000024  00000000  00000000  000000b4  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      00000012  00000000  00000000  000000d8  2**0
                  CONTENTS, READONLY
SYMBOL TABLE:
00000000 l    df *ABS*  00000000 main.c
00000000 l    d  .text  00000000 .text
00000000 l    d  .data  00000000 .data
00000000 l    d  .bss   00000000 .bss
00000000 l    d  .rodata.str1.1 00000000 .rodata.str1.1
00000000 l    d  .comment       00000000 .comment
00000000 g     F .text  0000007f main
00000000         *UND*  00000000 init_video
00000000         *UND*  00000000 gdt_install
00000000         *UND*  00000000 putch
00000000         *UND*  00000000 puts
00000018       O *COM*  00000001 gdt
00000006       O *COM*  00000001 gdtp

Мой скрипт ссылки (не уверен, будет ли он актуален):

OUTPUT_FORMAT("binary")
ENTRY(start)
phys = 0x00100000;
SECTIONS
{
  .text phys : AT(phys) {
    code = .;
    *(.text)
    *(.rodata*)
    . = ALIGN(4096);
  }
  .data . : AT(data)
  {
    data = .;
    *(.data)
    . = ALIGN(4096);
  }
  .bss . : AT(bss)
  {
    bss = .;
    *(.bss)
    . = ALIGN(4096);
  }
  end = .;
}

Если я закомментирую вызов putch в main.c, я вместо этого получаю неопределенные ссылки на put ... если я удалю вызов gdt_install, ошибок не будет!

gdt_install находится в файле C, но gdt_install вызывает функцию, которая определена в gdt.asm.

void gdt_install() {
    /* ... */
    gdt_reset();
}

[bits 32]
[section .text]
global gdt_reset
extern gdtp

gdt_reset:
    lgdt [gdtp]
    mov ax, 0x10      ; 0x10 offset for data segment (sizeof(struct gdt_entry) * 2)
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    jmp 0x08:gdt_reset2   ; 0x08 offset for code segment (sizeof(struct gdt_entry))
gdt_reset2:
    ret              ; ret back to C

Чтобы попытаться дополнительно диагностировать причину, я играл, пытаясь воссоздать ошибки. Если я перемещу вызов функции gdt_install () в определенное место в исходном коде, я не получаю никаких ошибок, и все работает нормально:

int main() {        

    init_video();

    putch('A');

    puts("<- print a single char there...\n");

    gdt_install();
    puts("asdf\n\n");

    int i;

    for (i = 0; i < 15; ++i) {
        if (i % 2 == 0) {
            puts("even\n");
        } else {
            puts("odd\n");
        }
    }

    return 0;
}

Если я перемещаю вызов над первым вызовом put (), я получаю неопределенные ссылки на put !:

...
init_video();

putch('A');

gdt_install();

puts("<- print a single char there...\n");

puts("asdf\n\n");

...



i586-elf-ld -T link.ld -o kernel32.bin kernel_loader.o main.o stdio.o common.o gdt.o gdt_asm.o
main.o: In function `main':
main.c:(.text+0x2b): undefined reference to `puts'
main.c:(.text+0x37): undefined reference to `puts'
main.c:(.text+0x51): undefined reference to `puts'
main.c:(.text+0x63): undefined reference to `puts'

Затем, если я перемещаю вызов выше putch (), он вызывает неопределенную ссылку на putch (где у меня изначально был вызов):

...
init_video();

gdt_install();

putch('A');

puts("<- print a single char there...\n");

puts("asdf\n\n");

...

main.o: In function `main':
main.c:(.text+0x1f): undefined reference to `putch'

И, наконец, выше init_video () вызывает неопределенную ссылку на init_video:

...
gdt_install();

init_video();

putch('A');

puts("<- print a single char there...\n");

puts("asdf\n\n");

...

main.o: In function `main':
main.c:(.text+0x15): undefined reference to `init_video'

Что, черт возьми, вызывает эту ошибку? Это похоже на то, что вызов gdt_install каким-то образом "развращает" другие символы ... Я не смог найти ссылку на него в каких-либо документах, но есть ли способ, которым вызов функции gdt_install мог привести к выходу за пределы некоторой "границы" компоновщика, разрушающей другой код?

Кто-нибудь сталкивался с подобной проблемой или есть идеи относительно дальнейшего расследования? Я написал на форуме osdev: http://forum.osdev.org/viewtopic.php?f=1&t=22227, но не очень повезло.

Спасибо

Изменить:

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

По запросу, вот файл main.c до и после предварительной обработки и дизассемблированный из скомпилированного файла main.o.

перед предварительной обработкой:

#include <stdio.h>
#include <common.h>
#include <gdt.h>

int main() {        
    init_video();

    putch('A');

    gdt_install();

    puts("<- print a single char there...\n");

    puts("asdf\n\n");

    int i;

    for (i = 0; i < 15; ++i) {
        if (i % 2 == 0) {
            puts("even\n");
        } else {
            puts("odd\n");
        }
    }

    return 0;
}

После предварительной обработки:

i586-elf-gcc -Wall -O -fstrength-reduce -fomit-frame-pointer -fno-inline -nostdinc -nostdlib -fsigned-char -nostartfiles -nodefaultlibs -fno-builtin -fno-stack-protector -I./include -E main.c
# 1 "main.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "main.c"
# 1 "./include/stdio.h" 1



# 1 "./include/common.h" 1



typedef unsigned short ushort;
typedef unsigned char uchar;
typedef unsigned int uint;
typedef unsigned long ulong;
typedef int size_t;

void *memcpy(void *dst, const void *src, size_t n);
void *memset(void *dst, const char val, size_t n);
void *memsetw(void *dst, const ushort val, size_t n);
void *memseti(void *dst, const int val, size_t n);
# 5 "./include/stdio.h" 2

void cls();
void writech(char c);
void putch(char c);
void puts(char *str);
void set_text_colour(uchar f, uchar b);
void init_video();
size_t strlen(char *str);
# 2 "main.c" 2

# 1 "./include/gdt.h" 1





struct gdt_entry {
    ushort limit_low;
    ushort base_low;
    uchar base_middle;
    uchar access;
    uchar granularity;
    uchar base_high;
} __attribute__((packed));

struct gdt_ptr {
    ushort limit;
    uint base;
} __attribute__((packed));

void gdt_set_gate(int n, ulong base, ulong limit, uchar access, uchar gran);
void gdt_install();

extern void gdt_reset();
# 4 "main.c" 2

int main() {
    init_video();

    putch('A');

    gdt_install();

    puts("<- print a single char there...\n");

    puts("asdf\n\n");

    int i;

    for (i = 0; i < 15; ++i) {
        if (i % 2 == 0) {
            puts("even\n");
        } else {
            puts("odd\n");
        }
    }

    return 0;
}

Изменить еще раз:

Спасибо nategoose за предложение -g3 для получения более приятного вывода при разборке:

main.o:     file format elf32-i386

SYMBOL TABLE:
00000000 l    df *ABS*  00000000 main.c
00000000 l    d  .text  00000000 .text
00000000 l    d  .data  00000000 .data
00000000 l    d  .bss   00000000 .bss
00000000 l    d  .rodata.str1.4 00000000 .rodata.str1.4
00000000 l    d  .rodata.str1.1 00000000 .rodata.str1.1
00000000 l    d  .stab  00000000 .stab
00000000 l    d  .stabstr   00000000 .stabstr
00000000 l    d  .comment   00000000 .comment
00000000 g     F .text  0000007f main
00000000         *UND*  00000000 init_video
00000000         *UND*  00000000 putch
00000000         *UND*  00000000 gdt_install
00000000         *UND*  00000000 puts



Disassembly of section .text:

00000000 <main>:
#include <stdio.h>
#include <common.h>
#include <gdt.h>

int main() {        
   0:   8d 4c 24 04             lea    0x4(%esp),%ecx
   4:   83 e4 f0                and    $0xfffffff0,%esp
   7:   ff 71 fc                pushl  -0x4(%ecx)
   a:   55                      push   %ebp
   b:   89 e5                   mov    %esp,%ebp
   d:   53                      push   %ebx
   e:   51                      push   %ecx
    init_video();
   f:   e8 fc ff ff ff          call   10 <main+0x10>

    putch('A');
  14:   83 ec 0c                sub    $0xc,%esp
  17:   6a 41                   push   $0x41
  19:   e8 fc ff ff ff          call   1a <main+0x1a>

    gdt_install();
  1e:   e8 fc ff ff ff          call   1f <main+0x1f>

    puts("<- print a single char there...\n");
  23:   c7 04 24 00 00 00 00    movl   $0x0,(%esp)
  2a:   e8 fc ff ff ff          call   2b <main+0x2b>

    puts("asdf\n\n");
  2f:   c7 04 24 00 00 00 00    movl   $0x0,(%esp)
  36:   e8 fc ff ff ff          call   37 <main+0x37>
  3b:   83 c4 10                add    $0x10,%esp

    int i;

    for (i = 0; i < 15; ++i) {
  3e:   bb 00 00 00 00          mov    $0x0,%ebx
        if (i % 2 == 0) {
  43:   f6 c3 01                test   $0x1,%bl
  46:   75 12                   jne    5a <main+0x5a>
            puts("even\n");
  48:   83 ec 0c                sub    $0xc,%esp
  4b:   68 07 00 00 00          push   $0x7
  50:   e8 fc ff ff ff          call   51 <main+0x51>
  55:   83 c4 10                add    $0x10,%esp
  58:   eb 10                   jmp    6a <main+0x6a>
        } else {
            puts("odd\n");
  5a:   83 ec 0c                sub    $0xc,%esp
  5d:   68 0d 00 00 00          push   $0xd
  62:   e8 fc ff ff ff          call   63 <main+0x63>
  67:   83 c4 10                add    $0x10,%esp

    puts("asdf\n\n");

    int i;

    for (i = 0; i < 15; ++i) {
  6a:   43                      inc    %ebx
  6b:   83 fb 0f                cmp    $0xf,%ebx
  6e:   75 d3                   jne    43 <main+0x43>
            puts("odd\n");
        }
    }

    return 0;
}
  70:   b8 00 00 00 00          mov    $0x0,%eax
  75:   8d 65 f8                lea    -0x8(%ebp),%esp
  78:   59                      pop    %ecx
  79:   5b                      pop    %ebx
  7a:   5d                      pop    %ebp
  7b:   8d 61 fc                lea    -0x4(%ecx),%esp
  7e:   c3                      ret    

А теперь новый вывод чистой make:

$ make
nasm -f elf kernel_loader.asm -o kernel_loader.o
i586-elf-gcc -Wall -O0 -fstrength-reduce -fomit-frame-pointer -fno-inline -nostdinc -nostdlib -fsigned-char -nostartfiles -nodefaultlibs -fno-builtin -fno-stack-protector -I./include -g3 -c main.c
i586-elf-gcc -Wall -O0 -fstrength-reduce -fomit-frame-pointer -fno-inline -nostdinc -nostdlib -fsigned-char -nostartfiles -nodefaultlibs -fno-builtin -fno-stack-protector -I./include -g3 -c stdio.c
i586-elf-gcc -Wall -O0 -fstrength-reduce -fomit-frame-pointer -fno-inline -nostdinc -nostdlib -fsigned-char -nostartfiles -nodefaultlibs -fno-builtin -fno-stack-protector -I./include -g3 -c common.c
i586-elf-gcc -Wall -O0 -fstrength-reduce -fomit-frame-pointer -fno-inline -nostdinc -nostdlib -fsigned-char -nostartfiles -nodefaultlibs -fno-builtin -fno-stack-protector -I./include -g3 -c gdt.c
nasm -f elf gdt.asm -o gdt_asm.o
i586-elf-ld -T link.ld -o kernel32.bin -\( kernel_loader.o main.o stdio.o common.o gdt.o gdt_asm.o -\)

main.o: In function `main':
/cygdrive/c/programming/os/kernel/main.c:12: undefined reference to `puts'
/cygdrive/c/programming/os/kernel/main.c:14: undefined reference to `puts'
/cygdrive/c/programming/os/kernel/main.c:20: undefined reference to `puts'
/cygdrive/c/programming/os/kernel/main.c:22: undefined reference to `puts'
make: *** [kernel32.bin] Error 1

Изменить 3

Как и просили, вот вывод nm -s на stdio.o

i586-elf-nm -s stdio.o

00000042 T cls
00000000 D colour
00000000 T init_video
         U memcpy
         U memsetw
0000015e T newline
00000004 D numcols
00000008 D numrows
000001e4 T putch
0000024e T puts
00000004 C screen_mem
000000b8 T scroll
00000291 T set_text_colour
00000016 T strlen
00000199 T writech
00000000 B xpos
00000004 B ypos

Редактировать 4 В соответствии с запросом, вот все исходные файлы. Я загрузил файлы в виде zip-архива по адресу: http://www.owenstephens.co.uk/media/files/kernel.zip Спасибо за постоянный интерес и помощь, мы очень ценим это!

Makefile:

NASM=nasm
GCC=i586-elf-gcc
LD=i586-elf-ld
FMT=-f elf
GFLAGS=-Wall -O0 -fstrength-reduce -fno-inline -nostdinc -nostdlib -fsigned-char -nostartfiles -nodefaultlibs -fno-builtin -fno-stack-protector -I./include -g3 -c 
LFLAGS=-T link.ld
ALL=kernel_loader.o main.o stdio.o common.o gdt.o gdt_asm.o
INCLUDES=include/stdio.h include/common.h include/gdt.h

all: $(ALL) kernel32.bin

kernel_loader.o: kernel_loader.asm
    $(NASM) $(FMT) $*.asm -o $@

main.o: main.c
    $(GCC) $(GFLAGS) $<

stdio.o: stdio.c include/stdio.h
    $(GCC) $(GFLAGS) $<

common.o: common.c include/common.h
    $(GCC) $(GFLAGS) $<

gdt.o: gdt.c include/gdt.h
    $(GCC) $(GFLAGS) $<

gdt_asm.o: gdt.asm
    $(NASM) $(FMT) $< -o $@

kernel32.bin: $(ALL) $(INCLUDES)
    $(LD) $(LFLAGS) -o $@ -\( $(ALL) -\)

clean:
    rm -f $(ALL) kernel32.bin

Скрипт ссылки:

OUTPUT_FORMAT("binary")
ENTRY(_start)
phys = 0x00100000;
SECTIONS
{
  .text phys : AT(phys) {
    code = .;
    *(.text)
    *(.rodata*)
    . = ALIGN(4096);
  }
  .data . : AT(data)
  {
    data = .;
    *(.data)
    . = ALIGN(4096);
  }
  .bss . : AT(bss)
  {
    bss = .;
    *(.bss)
    . = ALIGN(4096);
  }
  end = .;
}

main.c:

#include <stdio.h>
#include <common.h>
#include <gdt.h>

int main() {        
    gdt_install();

    puts("This is a minimal example...");

    return 0;
}

common.c:

#include <common.h>

void *memcpy(void *dst, const void *src, size_t n) { return (void *)0; }

void *memset(void *dst, const char val, size_t n) { return (void *)0; }

void *memsetw(void *dst, const ushort val, size_t n) { return (void *)0; }

void *memseti(void *dst, const int val, size_t n) { return (void *)0; }

stdio.c:

#include <stdio.h>
#include <common.h>

ushort *screen_mem;
int colour = 0x0F;
int xpos = 0, ypos = 0;
int numcols = 80, numrows = 25;

void init_video() {}

size_t strlen(char *str) { return 0; }

void cls() { }

inline void scroll() { }

inline void newline() { }

void writech(char c) { }

void putch(char c) { }

void puts(char *str) { }

void set_text_colour(unsigned char f, unsigned char b){ }

gdt.c:

#include <gdt.h>

struct gdt_entry gdt[3];
struct gdt_ptr gdtp;

void gdt_set_gate(int n, ulong base, ulong limit, uchar access, uchar gran) { }

void gdt_install() { }

gdt.asm:

global gdt_reset

gdt_reset:
    ret
gdt_reset2:
    ret

include / common.h:

#ifndef __COMMON_H
#define __COMMON_H

typedef unsigned short  ushort;
typedef unsigned char   uchar;
typedef unsigned int    uint;
typedef unsigned long   ulong;
typedef int size_t;

void *memcpy(void *dst, const void *src, size_t n);
void *memset(void *dst, const char val, size_t n);
void *memsetw(void *dst, const ushort val, size_t n);
void *memseti(void *dst, const int val, size_t n);
#endif

include / stdio.h:

#ifndef __STDIO_H
#define __STDIO_H

#include <common.h>

void cls();
void writech(char c);
void putch(char c);
void puts(char *str);
void set_text_colour(uchar f, uchar b);
void init_video();
size_t strlen(char *str);

#endif

include / gdt.h:

#ifndef __GDT_H
#define __GDT_H

#include <common.h>

struct gdt_entry {
    ushort  limit_low;
    ushort  base_low;
    uchar   base_middle;
    uchar   access;
    uchar   granularity;
    uchar   base_high;
} __attribute__((packed));

struct gdt_ptr {
    ushort limit;
    uint base;
} __attribute__((packed));

void gdt_set_gate(int n, ulong base, ulong limit, uchar access, uchar gran);
void gdt_install();

extern void gdt_reset();

#endif

Вывод команды "objdump -t" в общую библиотеку, которая включает все файлы .o (кроме kernel_loader, отсюда неопределенный символ _start.

> i586-elf-objdump -t libos.so.1.0.1

libos.so.1.0.1:     file format elf32-i386

SYMBOL TABLE:
08048080 l    d  .text  00000000 .text
08048162 l    d  .rodata    00000000 .rodata
08049180 l    d  .data  00000000 .data
0804918c l    d  .bss   00000000 .bss
00000000 l    d  .stab  00000000 .stab
00000000 l    d  .stabstr   00000000 .stabstr
00000000 l    d  .comment   00000000 .comment
00000000 l    df *ABS*  00000000 main.c
00000000 l    df *ABS*  00000000 stdio.c
00000000 l    df *ABS*  00000000 common.c
00000000 l    df *ABS*  00000000 gdt.c
00000000 l    df *ABS*  00000000 gdt.asm
08048161 l       .text  00000000 gdt_reset2
08049180 g     O .data  00000004 colour
08048125 g     F .text  00000014 memsetw
0804918c g     O .bss   00000004 xpos
08049188 g     O .data  00000004 numrows
08048158 g     F .text  00000005 gdt_install
08048108 g     F .text  0000000a memcpy
080480ee g     F .text  00000005 puts
08049198 g     O .bss   00000018 gdt
08049194 g     O .bss   00000004 screen_mem
080480e0 g     F .text  0000000e putch
08048144 g     F .text  00000014 gdt_set_gate
00000000         *UND*  00000000 _start
08048160 g       .text  00000000 gdt_reset
080480b4 g     F .text  00000005 init_video
080480c8 g     F .text  00000005 scroll
0804918c g       *ABS*  00000000 __bss_start
08048112 g     F .text  00000013 memset
08048080 g     F .text  00000033 main
080480f3 g     F .text  00000014 set_text_colour
080480cd g     F .text  00000005 newline
08049190 g     O .bss   00000004 ypos
080491b0 g     O .bss   00000006 gdtp
0804918c g       *ABS*  00000000 _edata
080491b8 g       *ABS*  00000000 _end
080480c3 g     F .text  00000005 cls
080480b9 g     F .text  0000000a strlen
08048139 g     F .text  0000000a memseti
08049184 g     O .data  00000004 numcols
080480d2 g     F .text  0000000e writech

person owst    schedule 15.07.2010    source источник
comment
Я столкнулся с этим однажды, некоторое время назад. Я думаю, это как-то связано с моим файлом libc.a. Вы уверены, что putch и put есть в вашей библиотеке и что их прототипы в этой библиотеке соответствуют вашему использованию?   -  person nmichaels    schedule 15.07.2010
comment
+1 Это очень хорошо написанный вопрос.   -  person e.James    schedule 15.07.2010
comment
Я реализовал свои собственные функции psuedo-stdio (put, putch ...), функции точно совпадают - это действительно странно, только когда я добавляю вызов функции install_gdt, все это ломается.   -  person owst    schedule 15.07.2010
comment
Не знаю, что это может означать и куда он может пойти, но вы говорите, что gdt_install находится в файле C, но дамп main.o указывает, что он не определен (внешний по отношению к main.o).   -  person Michael Burr    schedule 15.07.2010
comment
Да, так как я скомпилировал каждый файл C отдельно. (Вы заметите, что функции stdio также не определены). Я делаю отдельную ссылку, так как хочу использовать конкретную спецификацию (в основном, плоский двоичный файл, а не elf, pe или иначе). Извините не особо понятно, все идеи приветствуются!   -  person owst    schedule 15.07.2010
comment
Ваш сценарий компоновщика устанавливает символ входа в start - в каком файле определено start?   -  person Aidan Cully    schedule 15.07.2010
comment
В небольшом asm-скрипте - kernel_loader.asm, который включен в список файлов ссылок.   -  person owst    schedule 15.07.2010
comment
цвет написан неправильно. Убедитесь, что у вас есть британская версия библиотеки :)   -  person Hans Passant    schedule 15.07.2010
comment
Хорошо, если бы это было так, поверьте мне! (Я сам написал все функции библиотеки, так что, по крайней мере, я не могу винить ошибки в глупой орфографии!)   -  person owst    schedule 15.07.2010
comment
Как выглядит stdio.c? Или что на выходе i586-elf-nm -s stdio.o?   -  person JayM    schedule 21.07.2010
comment
Я добавил результат nm для stdio.o выше.   -  person owst    schedule 21.07.2010
comment
Так что stdio.o выглядит нормально. Можете ли вы попробовать опубликовать весь свой источник? Возможно, вырежьте содержимое функций до минимума, который вызывает эту ошибку. Если кто-то другой может скопировать ошибку, это упростит ее исправление.   -  person JayM    schedule 22.07.2010
comment
Просто в информационных целях, не могли бы вы собрать все, кроме начальной сборки, как разделяемую библиотеку, и посмотреть, имеет ли objdump -t смысл в отношении того, что определено, а что нет в этом модуле?   -  person nategoose    schedule 22.07.2010
comment
@JayM: Я удалил исходные файлы, но все равно ошибка. Я включил исходные файлы в редактирование (я также заархивировал их и разместил на своем сайте). Спасибо!   -  person owst    schedule 23.07.2010
comment
@nategoose: я создал общую библиотеку, и кажется, что все символы определены. Я включил вывод на тот случай, если вы заметите что-то очевидное. Спасибо!   -  person owst    schedule 23.07.2010
comment
Но что это за длинное лицо, мистер Старбак? не хочешь ли ты преследовать белого кита! искусство не игра для Моби Дика?   -  person BlueRaja - Danny Pflughoeft    schedule 23.07.2010
comment
Я загрузил ваш код и смог без проблем собрать. Где вы взяли свой набор инструментов? Какая у вас ОС?   -  person JayM    schedule 23.07.2010
comment
Черт, это отстой (в том смысле, что мы преследуем проблему с набором инструментов!). Я использую Win 7 x64 с кросс-компилятором, который я построил из исходного кода, ориентированного на i586-elf.   -  person owst    schedule 23.07.2010
comment
Я выполнил следующие инструкции: wiki.osdev.org/GCC_Cross-Compiler Я только что попытался переустановить, но безуспешно. Есть ли способ проверить установку / сборку gcc / binutils?   -  person owst    schedule 23.07.2010
comment
Я построил эту цепочку инструментов, и ваш код по-прежнему работает нормально. Вот еще пара вещей, которые вы можете попробовать 1) Убедитесь, что ваши исходные файлы и скрипт компоновщика имеют согласованные окончания строк - все CR или все CR / LF. 2) Попробуйте собрать свой собственный набор инструментов. 3) Попробуйте разные версии gcc / binutils.   -  person JayM    schedule 23.07.2010
comment
Хм, все равно не повезло! 1) Ага, проверил, все согласованы. Пытался изменить все на \ n, но безуспешно. 2) Пробовал, но обнаружил ошибки при связывании файлов PE в двоичный вывод. 3) По-прежнему не удалось при использовании другой версии как gcc, так и binutils. Какие версии gcc / binutils вам подходят?   -  person owst    schedule 24.07.2010


Ответы (7)


Это похоже на проблему с круговой ссылкой на линии связи. Компоновщик просматривает объектные файлы по порядку и «запоминает» все неразрешенные внешние элементы. Однако он также может отбрасывать любые объектные файлы, на которые нет ссылок. Если два или более объектных файла ссылаются друг на друга (вызывая циклическую ссылку), компоновщик не сможет отслеживать неразрешенные объекты.

Попробуйте продублировать части линии связи, а затем сузить ее до того, что вам нужно.

i586-elf-ld -T link.ld -o kernel32.bin kernel_loader.o main.o stdio.o common.o gdt.o gdt_asm.o \
        stdio.o common.o gdt.o gdt_asm.o
person JayG    schedule 19.07.2010
comment
Это, кажется, приводит к множественному определению ошибок ‹function› и большему количеству неопределенных ссылок на разные символы, так что не повезло! - person owst; 19.07.2010

Порядок файлов в командной строке, кажется, имеет значение для компоновщика GNU. Поместите файл .o, содержащий точку входа (kernel_loader.o), сначала в командную строку, затем любые объекты, на которые он напрямую ссылается, затем объекты, на которые ссылаются эти объекты (и которые еще не находятся в строке cmd) и т. Д., Или возможно, что компоновщик пропустит некоторые файлы.

person Aidan Cully    schedule 15.07.2010
comment
Ага! Я надеялся, что кто-то другой предложит это, я думал, что решил проблему таким образом. Я не знаю, является ли это какой-то проблемой в стиле круговой ссылки, но после перестановки я попал в ситуацию, когда перемещение / вызов функций снова приводило к сбою ссылки ... - person owst; 15.07.2010
comment
Да, похоже, это работает. Если я заказываю объектные файлы в порядке их включения (а затем их функций), кажется, что он работает ... Однако, когда я перемещаю проблемный вызов gdt_install в верхнюю часть исходного файла main.c, я получаю undefined использованная литература. - person owst; 15.07.2010
comment
Попробуйте поместить объектные файлы (кроме точки входа) в архив с помощью (rm -f libfoo.a ; ar -cr libfoo.a a.o b.o c.o ; ranlib libfoo.a), а затем связать с libfoo.a вместо отдельных объектных файлов. - person Aidan Cully; 15.07.2010
comment
Нет, нет! Это все еще ошибки, требующие неопределенной ссылки, как и раньше. - person owst; 15.07.2010
comment
Попробуйте заключить список объектных файлов в -Wl, - (и -Wl, -) (вам может потребоваться экранировать скобки с помощью обратной косой черты). - person zwol; 16.07.2010
comment
Я никогда раньше не видел этих вариантов ссылок, они выглядят так, как будто они должны решить проблему. Тем не менее, я получаю сообщение об ошибке от ld: kernel_lib.a, не могу прочитать символы: неверное значение Поиск в Google, похоже, предполагает, что это может быть какая-то проблема только с 64-разрядной версией (я использую Win 7 x64, но я используя мой кросс-компилятор, нацеленный на elf32-i386, если это помогает). Какие-либо предложения? - person owst; 16.07.2010
comment
Я также пробовал со списком объектных файлов, согласно вашему предложению. К сожалению, это дает ту же неопределенную ошибку ссылки. - person owst; 16.07.2010
comment
@owst: Если все остальное не помогает, вы можете попробовать стиль «функция для каждого файла», возможно, в сочетании с методом архивирования, который я предложил в предыдущем комментарии. - person Aidan Cully; 21.07.2010
comment
Я попробую. Не могли бы вы объяснить свои доводы (так что я знаю, что я ищу!)? Что будет делать метод архивирования, чего не происходит при использовании отдельных объектных файлов? Кроме того, какие функции вы предлагаете разместить в отдельных файлах, все? Спасибо за идеи! - person owst; 21.07.2010
comment
@owst: работая в предположении, что в конечном итоге вы хотите, чтобы ваш компоновщик работал надежно, а не полностью понимал, что происходит (что я все еще не могу полностью объяснить, хотя у меня есть некоторое представление), цель состоит в том, чтобы упростить задача компоновщика. Компоновщик связывает код на уровне детализации объектного файла, поэтому, если у вас есть две функции в объекте, только одна из которых используется, компоновщик все равно будет сканировать неразрешенные ссылки из другой функции и выдавать ошибки, если любая из этих ссылок может ' t быть решенным. Используя функцию "файл на функцию", вы не получите такого эффекта. - person Aidan Cully; 21.07.2010
comment
Кроме того, причина помещения объектов в архив заключается в том, что компоновщик может по существу игнорировать любые проблемы с порядком в самой командной строке - ваш файл .o для точки входа предшествует файлу .a, содержащему все ваши библиотечные подпрограммы в cmd Линия, поэтому компоновщик имеет возможность сканировать файлы .o из архива по мере необходимости, вместо того, чтобы сканировать только файлы .o в строке cmd. (Я думал, что использование архива решило бы вашу проблему раньше, но было очевидно, что это было неправильно.) Связывая файл-функцию с файлом .a, вам не нужно беспокоиться о порядке строк cmd. - person Aidan Cully; 21.07.2010
comment
Да, вы правы, я хочу, чтобы все заработало! :) Конечно, всегда лучше понять, почему и как работает исправление, но я не обязательно беспокоюсь в этом случае ... Я попробую ваши предложения сегодня после работы, и посмотрю, как это пойдет. Спасибо за помощь! - person owst; 21.07.2010

Существует еще один вопрос SO (может быть похожим / идентичным, я не уверен), который охватывает некоторые из этого. Предлагает ли это какую-нибудь помощь?

person e.James    schedule 15.07.2010
comment
Похоже, код взят из аналогичного (если не того же) учебника. Я проверю, спасибо. Одно отличие состоит в том, что его ошибка возникает во время выполнения, а моя - во время компоновки ... но мы посмотрим :) - person owst; 15.07.2010

Я видел похожие проблемы несколько раз, и в определенный момент, незадолго до того, как я схожу с ума, я начинаю искать невидимые вещи, которые могут оказаться в именах. Байты не-ASCII или непечатаемые символы ASCII, которые могли проникнуть в ваш исходный код и присоединиться к первому коду, отображаемому после gdt_install();

Вы можете попробовать добавить комментарий или пустой макрос (или do{}while(0)) между вашим вызовом gdt_install() и следующей реальной строкой кода. Может быть, даже поместите курсор в имя функции и сделайте резервную копию непосредственно перед первым символом этого имени функции и начните вводить все, что вы решите добавить туда. Если это что-то вызвано присутствием gdt_install();, тогда что-то еще, добавленное туда, должно вызвать другую ошибку.

Если вы еще этого не сделали, вы можете просмотреть вывод препроцессора файла с вызовом gdt_install(), а также вывод его сборки.

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

Надеюсь, ты в этом поймёшь.

person nategoose    schedule 20.07.2010
comment
Хорошая идея относительно символов, отличных от ascii; к сожалению, я ничего не заметил. Изменение имени не повлекло за собой разницы в поведении. Я добавлю (при редактировании) вывод препроцессора и сборки для файла с проблемным вызовом. Спасибо за идеи! - person owst; 21.07.2010
comment
Вывод сборки gcc, вероятно, был бы более читабельным по сравнению с метками. - person nategoose; 21.07.2010
comment
@owst: попробуйте добавить -g3 к своим командам компиляции и посмотрите, изменятся ли ошибки компоновщика. ld использует символы отладки для генерации ошибок, которые больше связаны с исходным кодом. Вы всегда можете удалить двоичный файл после сборки. Кроме того, если вы еще этого не сделали, вы можете попытаться построить с -O0 только для вывода ошибок. - person nategoose; 21.07.2010
comment
Ошибки компоновщика теперь более наглядны (теперь используется имя файла / строка вместо смещений), но они, похоже, показывают то же сообщение об ошибке. Я использовал -O, который, как мне казалось, был таким же, как -O0, но я тоже пробовал, на всякий случай. Кажется, нет большой разницы в сгенерированном выходе ... - person owst; 21.07.2010
comment
@owst: -O совпадает с -O1. - person nategoose; 21.07.2010

какое-то безумное предположение, возможно, ваша функция ассемблера, которая вызывается (и, вероятно, встроена) в gdt_install, портит то, что будет после нее. (этот прыжок в конце перед ret необычен, никогда такого не видел)

Ваш gdt_install находится в том же блоке компиляции, что и у вас есть вызов? Вы используете -O[12] для компиляции? Вызов встроен? Как выглядит ассемблер, создаваемый компилятором для стороны вызова?

Вы пробовали компилировать с -O0 -g или -fno-inline (или как эта опция называется)?

person Jens Gustedt    schedule 20.07.2010
comment
Переход используется для установки правильного значения регистра CS (дескриптора кода). Я не думаю, что функция ассемблера может быть встроена, поскольку она компилируется отдельно (с Nasm), хотя я вполне мог ошибаться. У меня уже отключена вся оптимизация и встраивание. - person owst; 21.07.2010

Вы написали. "I've implemented my own psuedo-stdio functions (puts, putch...) the functions match exactly ..." Вы используете что-нибудь в стандартной libc? Если нет, вам следует добавить -nostdlib в командную строку. Я видел странные вещи, когда пытался переопределить функции в стандартной библиотеке libc.

person JayM    schedule 20.07.2010
comment
Нет, я ничего не использую из std libc; У меня уже есть этот флаг. Спасибо хоть! - person owst; 21.07.2010

Я подозреваю, что, возможно, объединение таблиц символов связи из объекта, созданного nasm, и объектов, созданных gcc / gas, может что-то испортить.

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

Еще одна вещь, которая пришла мне в голову, - это то, что если gtd_install написан на языке ассемблера, то вполне возможно, что это может быть не на 100% синтаксически правильным. Я никогда не видел ничего подобного, но просто подумал, что возможно, что границы gtd_install (особенно конец) или его размер неправильно определены ассемблером, и это просто дает случайные результаты.

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

person nategoose    schedule 21.07.2010