0

System: Centos

I have a C++ file, a assembly file and a GYP file:

C++ file: src/node_main.cc

#include <iostream>
using namespace std;
extern "C" {
extern char __attribute__((weak)) global_variable;
}  // extern "C"


int main()
{

    printf("global_variable:%p", reinterpret_cast<uintptr_t>(&global_variable));
    return 0;
}

Assembly file: src/global_variable.S

.global global_variable

global_variable:

GYP file: node.gyp

{
    'targets': [
        {
            'target_name': 'global_variable',
            'type': 'none',
            'conditions': [
                [ 'OS in "linux freebsd solaris"', {
                  'type': 'static_library',
                  'sources': [
                    'src/global_variable.S'
                  ]
                }],
            ]
        },
        {
            'target_name': 'myexperiment',
            'type': 'executable',
            'sources': [
                'src/node_main.cc'
            ],
            'dependencies': [ 'global_variable' ],
            'conditions': [
                [ 'OS in "linux freebsd"', {
                  'dependencies': [ 'global_variable' ],
                #   'ldflags+': [
                #     '<(PRODUCT_DIR)/obj.target/global_variable/src/global_variable.o'
                #   ]
                }]
            ]
        }
    ]
}

My build command:

python3 main.py
make

My question: When I comment out the "ldflags+", I can't access "global_variable":

./out/Default/myexperiment

Execute "myexperiment" will print "global_variable:(nil)".

When I use the "ldflags", I can access "global_variable", it will print "global_variable:0x4006e6".

I used "make -n" command and tried to see what happened to g++, using "ldflags+":

g++ -o out/Default/myexperiment out/Default/obj.target/global_variable/src/global_variable.o  -Wl,--start-group out/Default/obj.target/myexperiment/src/node_main.o out/Default/obj.target/libglobal_variable.a  -Wl,--end-group

not using "ldflags+":

g++ -o out/Default/myexperiment   -Wl,--start-group out/Default/obj.target/myexperiment/src/node_main.o out/Default/obj.target/libglobal_variable.a  -Wl,--end-group

It seems the only difference is: "global_variable.o".

If "global_variable.o" is added, the global variable can be accessed, otherwise the global variable is nil, despite "libglobal_variable.a" is added to both.

Thoroughly confused.

jasonjifly
  • 21
  • 2
  • 1
    Unrelated to your link error, note that you declared `extern char __attribute__((weak)) global_variable;` as a `char`, but you reserved zero bytes of storage at that location. I find that `extern char foo[]` is a better choice for a C type to attach to a symbol where you only ever want the address, like the the end of the `.text` or `.data` section, or something like that. (Rather than an actual variable, i.e. a name labeling some static storage.) – Peter Cordes Aug 01 '23 at 07:19
  • Also, if you're ever porting to MacOS, you'll want `extern char foo[] asm("_foo")` to set the asm symbol name to *not* include the `_` you'd get from `extern char foo[];` in MacOS's ABI. (Or change the `.s` file to use `_foo`, of course.) Other x86-64 targets (both Windows, and Linux and other ELF targets) don't mangle C names so your code does work if linked properly there, unlike 32-bit x86 Windows. – Peter Cordes Aug 01 '23 at 07:22
  • 1
    I don't see where `libglobal_variable.a` comes from. I'm not familiar with `gyp`, does it automatically put `.o` files into a library? – Barmar Aug 01 '23 at 07:27
  • The order of the files being linked seems to be wrong. `global_variable.o` needs to be after `node_main.o`. – Barmar Aug 01 '23 at 07:30
  • Code review of your [mcve]: `printf` is declared in `stdio.h` (or `cstdio` if you want to be strictly ISO C++), not `iostream`. Also, it doesn't compile on Linux; missing ``. Printf's `%p` conversion takes a `void*`, not `uintptr_t`. On mainstream platforms they have the same object representation, but if you're going to be sloppy about types, just omit the cast entirely and pass a `char*` instead of a `void*`. That's much *less* sloppy: [Correct format specifier to print pointer or address?](https://stackoverflow.com/q/9053658) . `gcc -Wall` will warn you about that. – Peter Cordes Aug 01 '23 at 07:35
  • Also, don't use `using namespace std`, and in this program you can simply remove it without breaking anything (especially if you include `` instead of ``). – Peter Cordes Aug 01 '23 at 07:35
  • Anyway, after making source that works with `g++ main.cpp symbol.S`, it still links after I do `ar r lib.a symbol.o` and `g++ main.cpp lib.a`. But it also links properly if I do `g++ symbol.cpp` without the asm at all, on x86-64 GNU/Linux. So maybe this is only reproducible on Windows, perhaps `weak` symbols work differently on ELF targets; still is enough of a definition to not leave the symbol undefined? – Peter Cordes Aug 01 '23 at 07:40
  • It's surely not responsible for your error, but semantically, you should be using a `static_cast` here, not a `reinterpret_cast`. – John Bollinger Aug 01 '23 at 21:33

1 Answers1

2

Today I tried a lot of experiments, it took me two days to figure it out.

Finally I found the key: __attribute__((weak))

When using weak with static library, the linker will not try to find a definition from static library, so the definition will be discarded.

When linking with object file, the linker will unconditionally link it to the output file.

So it's unrelated to Assembler and GYP. It's because I didn't fully understand how .a file work with linker.

This answer helps me a lot: https://stackoverflow.com/questions/51656838/attribute-weak-and-static-libraries#:~:text=The%20weak%20attribute%20causes%20the,targets%2C%20and%20also%20for%20a.

Thank everyone!

jasonjifly
  • 21
  • 2