I'm using a book, and now I'm studying external variables. I'm supposed to use a function located in another file and return a value from that function. But I don't understand how does this happens exactly. This is the code provided by the book:
This is the code in the first file:
#include <stdio.h>
double getCircum(double);
double PI = 3.14;
int gi;
int main(void)
{
double r = 5.87;
const double PI = 3.14;
printf("%.f", getCircum(r));
}
This is the code in the other file:
external double PI;
double getCircum(double r)
{
return 2 * r * PI;
}
The output is supposed to be 36.88. but I keep getting an error message that the file is not the directory. I don't know what file it that. Also, I don't really get how the code is supposed to look for a function in a different file, is the code missing something?
This is possible through to the linking process.
When reading your C first file, the compiler will output an object file which contain unresolved references to symbols like printf, getCircum because they are declared (printf in <stdio.h> and getCircum in your first file) but not implemented (you didn't wrote them code in the first file).
When reading the second file, the compiler will miss the definition of PI.
This doesn't prevent him from producing valid object files. After removing the line double PI = 3.14 (it miss a ;), compile without linking with :
cc -c -o 1st_file.o 1st_file.c
cc -c -o 2nd_file.o 2nd_file.c
This will output two object files, if you run obj-dump -t 1st_file.o 2st_file.o you will see a list of provided and unresolved symbols of both files.
Now we will link with cc -o program.exe 1st_file.o 2nd_file.o. The C compiler will link both objects and some system-wide ones inside program.exe.
And voilà !
In your case cc -o program.exe 1st_file.c 2nd_file.c will do the job. But in real program, recompiling the whole program at each test take a lot of time, I have a private project which take 3,5 seconds to link vs 17,5 to recompile everything for 4k lines of code, Linux itself has more than a 2M lines of code...
Finally, post the console output and build command in the question, my answer make the important assumption that you are on a Linux with a working C compiler and GNU binutils, StackOverflow has a great doc here https://stackoverflow.com/help/how-to-ask to ask good question which have more chances to be solved
Edit: Defining PI 2 times here is not an error
I'm trying to compile and link a simple program to a DOS .com file using Turbo C compiler and linker. By that I try the simplest C-program I can think of.
void main()
{}
Are there command line arguments to link to com files in the Turbo C Linker?
The Error Message I get from the Linker is the following:
"Fatal: Cannot generate COM file: invalid entry point address"
I know that com files need entry point to be at 100h. Does Turbo C have an option to set this address?
It has been a long time since I have genuinely tried to use Turbo-C for this kind of thing. If you are compiling and linking on the command line separately with TCC.EXE and TLINK.EXE then this may work for you.
To compile and link to a COM file you can do this for each one of your C source files creating an OBJ file for each:
tcc -IF:\TURBOC3\INCLUDE -c -mt file1.c
tcc -IF:\TURBOC3\INCLUDE -c -mt file2.c
tcc -IF:\TURBOC3\INCLUDE -c -mt file3.c
tlink -t -LF:\TURBOC3\LIB c0t.obj file1.obj file2.obj file3.obj,myprog.com,myprog.map,cs.lib
Each C file is compiled individually using -mt (tiny memory model) to a corresponding OBJ file. The -I option specifies the path of the INCLUDE directory in your environment (change accordingly). The -c option tell TCC to compile to a OBJ file only.
When linking -t tells the linker to generate a COM program (and not an EXE), -LF:\TURBOC3\LIB is the path to the library directory in your environment (change accordingly). C0T.OBJ is the C runtime file for the tiny memory model. This includes the main entry point that you are missing. You then list all the other OBJ files separated by a space. After the first comma is the output file name. If using -t option name the program with a COM extension. After the second comma is the MAP file name (you can leave the file name blank if you don't want a MAP file). After the third comma is the list of libraries separated by spaces. With the tiny model you want to use the small model libraries. The C library for the small memory model is called CS.LIB .
As an example if we have a single source file called TEST.C that looks like:
#include<stdio.h>
int main()
{
printf("Hello, world!\n");
return 0;
}
If we want to compile and link this the commands would be:
tcc -IF:\TURBOC3\INCLUDE -c -mt test.c
tlink -t -LF:\TURBOC3\LIB c0t.obj test.obj,test.com,test.map,cs.lib
You will have to use the paths for your own environment. These commands should produce a program called TEST.COM. When run it should print:
Hello, world!
You can generate COM file while still using IDE to generate EXE. Following worked on TC 2.01. Change memory model to Tiny in the options, then compile the program and generate EXE file, then go to command prompt, and run EXE2BIN PROG.EXE PROG.COM. Replace PROG with your program name.
Your problem is about "entry point"
some compiler or linker can recognize void main() like entry point omiting a return value but no all of them.
You shoud use int main() entry point instead for better control of app and compiler can recognize main function as entry point
example:
int main() {
/* some compiler return 0 when you don't for main,
they can ask for return value */
}
from geekforgeeks:
A conforming implementation may provide more versions of main(), but they must all have return type int. The int returned by main() is a way for a program to return a value to “the system” that invokes it. On systems that doesn’t provide such a facility the return value is ignored, but that doesn’t make “void main()” legal C++ or legal C. Even if your compiler accepts “void main()” avoid it, or risk being considered ignorant by C and C++ programmers.
In C++, main() need not contain an explicit return statement. In that case, the value returned is 0, meaning successful execution.
source: https://www.geeksforgeeks.org/fine-write-void-main-cc/
I'm trying to write a simple system call on XV6 (documentation available here and Github here) in order to understand how they're implemented. I've used these steps
In syscall.c, declared extern int sys_hello(void) and added [SYS_hello] sys_hello into static int (*syscalls[])(void) array
In syscall.h, defined SYS_hello as call number 22
In user.h, declared the function prototype as int hello (void);
In usys.S, added SYSCALL(hello) to the macro
In sysproc.c, added the function sys_hello(void) at the bottom
int sys_hello(void)
{
cprintf ("Hello World System Call\n");
return 0;
}
Created hello.c which simply calls the hello() system call
Added hello.c to the Makefile and ran the code
It worked as expected.
Now, my question is that it seems that the array in syscall.c matches the indexes of the commands with the system call numbers in syscall.h file
However, if I move the hello position to the second spot in the syscall.c and let the system command number in syscall.h stay 22 the system command works as before. Where as, I expected that it'd break. Can you help me understand how the array syscall.c maps (if that's even the correct word) to the syscall.h system call number?
I'm fairly new to XV6 and C so please don't get mad at me if this question seems silly, I'm only trying to learn.
Here is the Github link to my fork of the XV6 repository if that helps: github.com/AdityaSingh/XV6
The array of syscalls is syscall.c makes use of the designated initialization syntax where you can specify at which index to assign the value.
static int (*syscalls[])(void) = {
[SYS_fork] sys_fork,
}
SYS_fork, defined as 1, specifies the index where to assign sys_fork. Thus the order of the elements doesn't matter using this syntax.
Is it possible to compile a C++ (or the like) program without generating the executable file but writing it and executing it directly from memory?
For example with GCC and clang, something that has a similar effect to:
c++ hello.cpp -o hello.x && ./hello.x $# && rm -f hello.x
In the command line.
But without the burden of writing an executable to disk to immediately load/rerun it.
(If possible, the procedure may not use disk space or at least not space in the current directory which might be read-only).
Possible? Not the way you seem to wish. The task has two parts:
1) How to get the binary into memory
When we specify /dev/stdout as output file in Linux we can then pipe into our program x0 that reads
an executable from stdin and executes it:
gcc -pipe YourFiles1.cpp YourFile2.cpp -o/dev/stdout -Wall | ./x0
In x0 we can just read from stdin until reaching the end of the file:
int main(int argc, const char ** argv)
{
const int stdin = 0;
size_t ntotal = 0;
char * buf = 0;
while(true)
{
/* increasing buffer size dynamically since we do not know how many bytes to read */
buf = (char*)realloc(buf, ntotal+4096*sizeof(char));
int nread = read(stdin, buf+ntotal, 4096);
if (nread<0) break;
ntotal += nread;
}
memexec(buf, ntotal, argv);
}
It would also be possible for x0 directly execute the compiler and read the output. This question has been answered here: Redirecting exec output to a buffer or file
Caveat: I just figured out that for some strange reason this does not work when I use pipe | but works when I use the x0 < foo.
Note: If you are willing to modify your compiler or you do JIT like LLVM, clang and other frameworks you could directly generate executable code. However for the rest of this discussion I assume you want to use an existing compiler.
Note: Execution via temporary file
Other programs such as UPX achieve a similar behavior by executing a temporary file, this is easier and more portable than the approach outlined below. On systems where /tmp is mapped to a RAM disk for example typical servers, the temporary file will be memory based anyway.
#include<cstring> // size_t
#include <fcntl.h>
#include <stdio.h> // perror
#include <stdlib.h> // mkostemp
#include <sys/stat.h> // O_WRONLY
#include <unistd.h> // read
int memexec(void * exe, size_t exe_size, const char * argv)
{
/* random temporary file name in /tmp */
char name[15] = "/tmp/fooXXXXXX";
/* creates temporary file, returns writeable file descriptor */
int fd_wr = mkostemp(name, O_WRONLY);
/* makes file executable and readonly */
chmod(name, S_IRUSR | S_IXUSR);
/* creates read-only file descriptor before deleting the file */
int fd_ro = open(name, O_RDONLY);
/* removes file from file system, kernel buffers content in memory until all fd closed */
unlink(name);
/* writes executable to file */
write(fd_wr, exe, exe_size);
/* fexecve will not work as long as there in a open writeable file descriptor */
close(fd_wr);
char *const newenviron[] = { NULL };
/* -fpermissive */
fexecve(fd_ro, argv, newenviron);
perror("failed");
}
Caveat: Error handling is left out for clarities sake. Includes for sake of brevity.
Note: By combining step main() and memexec() into a single function and using splice(2) for copying directly between stdin and fd_wr the program could be significantly optimized.
2) Execution directly from memory
One does not simply load and execute an ELF binary from memory. Some preparation, mostly related to dynamic linking, has to happen. There is a lot of material explaining the various steps of the ELF linking process and studying it makes me believe that theoretically possible. See for example this closely related question on SO however there seems not to exist a working solution.
Update UserModeExec seems to come very close.
Writing a working implementation would be very time consuming, and surely raise some interesting questions in its own right. I like to believe this is by design: for most applications it is strongly undesirable to (accidentially) execute its input data because it allows code injection.
What happens exactly when an ELF is executed? Normally the kernel receives a file name and then creates a process, loads and maps the different sections of the executable into memory, performs a lot of sanity checks and marks it as executable before passing control and a file name back to the run-time linker ld-linux.so (part of libc). The takes care of relocating functions, handling additional libraries, setting up global objects and jumping to the executables entry point. AIU this heavy lifting is done by dl_main() (implemented in libc/elf/rtld.c).
Even fexecve is implemented using a file in /proc and it is this need for a file name that leads us to reimplement parts of this linking process.
Libraries
UserModeExec
libelf -- read, modify, create ELF files
eresi -- play with elfes
OSKit (seems like a dead project though)
Reading
http://www.linuxjournal.com/article/1060?page=0,0 -- introduction
http://wiki.osdev.org/ELF -- good overview
http://s.eresi-project.org/inc/articles/elf-rtld.txt -- more detailed Linux-specific explanation
http://www.codeproject.com/Articles/33340/Code-Injection-into-Running-Linux-Application -- how to get to hello world
http://www.acsu.buffalo.edu/~charngda/elf.html -- nice reference of ELF structure
Loaders and Linkers by John Levine -- deeoer explanation of linking
Related Questions at SO
Linux user-space ELF loader
ELF Dynamic loader symbol lookup ordering
load-time ELF relocation
How do global variables get initialized by the elf loader
So it seems possible, you decide whether is also practical.
Yes, though doing it properly requires designing significant parts of the compiler with this in mind. The LLVM guys have done this, first with a kinda-separate JIT, and later with the MC subproject. I don't think there's a ready-made tool doing it. But in principle, it's just a matter of linking to clang and llvm, passing the source to clang, and passing the IR it creates to MCJIT. Maybe a demo does this (I vaguely recall a basic C interpreter that worked like this, though I think it was based on the legacy JIT).
Edit: Found the demo I recalled. Also, there's cling, which seems to do basically what I described, but better.
Linux can create virtual file systems in RAM using tempfs. For example, I have my tmp directory set up in my file system table like so:
tmpfs /tmp tmpfs nodev,nosuid 0 0
Using this, any files I put in /tmp are stored in my RAM.
Windows doesn't seem to have any "official" way of doing this, but has many third-party options.
Without this "RAM disk" concept, you would likely have to heavily modify a compiler and linker to operate completely in memory.
If you are not specifically tied to C++, you may also consider other JIT based solutions:
in Common Lisp SBCL is able to generate machine code on the fly
you could use TinyCC and its libtcc.a which emits quickly poor (i.e. unoptimized) machine code from C code in memory.
consider also any JITing library, e.g. libjit, GNU Lightning, LLVM, GCCJIT, asmjit
of course emitting C++ code on some tmpfs and compiling it...
But if you want good machine code, you'll need it to be optimized, and that is not fast (so the time to write to a filesystem is negligible).
If you are tied to C++ generated code, you need a good C++ optimizing compiler (e.g. g++ or clang++); they take significant time to compile C++ code to optimized binary, so you should generate to some file foo.cc (perhaps in a RAM file system like some tmpfs, but that would give a minor gain, since most of the time is spent inside g++ or clang++ optimization passes, not reading from disk), then compile that foo.cc to foo.so (using perhaps make, or at least forking g++ -Wall -shared -O2 foo.cc -o foo.so, perhaps with additional libraries). At last have your main program dlopen that generated foo.so. FWIW, MELT was doing exactly that, and on Linux workstation the manydl.c program shows that a process can generate then dlopen(3) many hundred thousands of temporary plugins, each one being obtained by generating a temporary C file and compiling it. For C++ read the C++ dlopen mini HOWTO.
Alternatively, generate a self-contained source program foobar.cc, compile it to an executable foobarbin e.g. with g++ -O2 foobar.cc -o foobarbin and execute with execve that foobarbin executable binary
When generating C++ code, you may want to avoid generating tiny C++ source files (e.g. a dozen lines only; if possible, generate C++ files of a few hundred lines at least; unless lots of template expansion happens thru extensive use of existing C++ containers, where generating a small C++ function combining them makes sense). For instance, try if possible to put several generated C++ functions in the same generated C++ file (but avoid having very big generated C++ functions, e.g. 10KLOC in a single function; they take a lot of time to be compiled by GCC). You could consider, if relevant, to have only one single #include in that generated C++ file, and pre-compile that commonly included header.
Jacques Pitrat's book Artificial Beings, the conscience of a conscious machine (ISBN 9781848211018) explains in details why generating code at runtime is useful (in symbolic artificial intelligence systems like his CAIA system). The RefPerSys project is trying to follow that idea and generate some C++ code (and hopefully, more and more of it) at runtime. Partial evaluation is a relevant concept.
Your software is likely to spend more CPU time in generating C++ code than GCC in compiling it.
tcc compiler "-run" option allows for exactly this, compile into memory, run there and finally discard the compiled stuff. No filesystem space needed. "tcc -run" can be used in shebang to allow for C script, from tcc man page:
#!/usr/local/bin/tcc -run
#include <stdio.h>
int main()
{
printf("Hello World\n");
return 0;
}
C scripts allow for mixed bash/C scripts, with "tcc -run" not needing any temporary space:
#!/bin/bash
echo "foo"
sed -n "/^\/\*\*$/,\$p" $0 | tcc -run -
exit
/**
*/
#include <stdio.h>
int main()
{
printf("bar\n");
return 0;
}
Execution output:
$ ./shtcc2
foo
bar
$
C scripts with gcc are possible as well, but need temporary space like others mentioned to store executable. This script produces same output as the previous one:
#!/bin/bash
exc=/tmp/`basename $0`
if [ $0 -nt $exc ]; then sed -n "/^\/\*\*$/,\$p" $0 | gcc -x c - -o $exc; fi
echo "foo"
$exc
exit
/**
*/
#include <stdio.h>
int main()
{
printf("bar\n");
return 0;
}
C scripts with suffix ".c" are nice, headtail.c was my first ".c" file that needed to be executable:
$ echo -e "1\n2\n3\n4\n5\n6\n7" | ./headtail.c
1
2
3
6
7
$
I like C scripts, because you just have one file, you can easily move around, and changes in bash or C part require no further action, they just work on next execution.
P.S:
The above shown "tcc -run" C script has a problem, C script stdin is not available for executed C code. Reason was that I passed extracted C code via pipe to "tcc -run". New gist run_from_memory_stdin.c does it correctly:
...
echo "foo"
tcc -run <(sed -n "/^\/\*\*$/,\$p" $0) 42
...
"foo" is printed by bash part, "bar 42" from C part (42 is passed argv[1]), and piped script input gets printed from C code then:
$ route -n | ./run_from_memory_stdin.c
foo
bar 42
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.29.58.98 0.0.0.0 UG 306 0 0 wlan1
10.0.0.0 0.0.0.0 255.255.255.0 U 0 0 0 wlan0
169.254.0.0 0.0.0.0 255.255.0.0 U 303 0 0 wlan0
172.29.58.96 0.0.0.0 255.255.255.252 U 306 0 0 wlan1
$
One can easily modify the compiler itself. It sounds hard first but thinking about it, it seams obvious. So modifying the compiler sources directly expose a library and make it a shared library should not take that much of afford (depending on the actual implementation).
Just replace every file access with a solution of a memory mapped file.
It is something I am about to do with compiling something transparently in the background to op codes and execute those from within Java.
-
But thinking about your original question it seams you want to speed up compilation and your edit and run cycle. First of all get a SSD-Disk you get almost memory speed (use a PCI version) and lets say its C we are talking about. C does this linking step resulting in very complex operations that are likely to take more time than reading and writing from / to disk. So just put everything on SSD and live with the lag.
Finally the answer to OP question is yes!
I found memrun repo from guitmz, that demoed running (x86_64) ELF from memory, with golang and assembler. I forked that, and provided C version of memrun, that runs ELF binaries (verified on x86_64 and armv7l), either from standard input, or via first argument process substitution. The repo contains demos and documentation (memrun.c is 47 lines of code only):
https://github.com/Hermann-SW/memrun/tree/master/C#memrun
Here is simplest example, with "-o /dev/fd/1" gcc compiled ELF gets sent to stdout, and piped to memrun, which executes it:
pi#raspberrypi400:~/memrun/C $ gcc info.c -o /dev/fd/1 | ./memrun
My process ID : 20043
argv[0] : ./memrun
no argv[1]
evecve --> /usr/bin/ls -l /proc/20043/fd
total 0
lr-x------ 1 pi pi 64 Sep 18 22:27 0 -> 'pipe:[1601148]'
lrwx------ 1 pi pi 64 Sep 18 22:27 1 -> /dev/pts/4
lrwx------ 1 pi pi 64 Sep 18 22:27 2 -> /dev/pts/4
lr-x------ 1 pi pi 64 Sep 18 22:27 3 -> /proc/20043/fd
pi#raspberrypi400:~/memrun/C $
The reason I was interested in this topic was usage in "C script"s. run_from_memory_stdin.c demonstrates all together:
pi#raspberrypi400:~/memrun/C $ wc memrun.c | ./run_from_memory_stdin.c
foo
bar 42
47 141 1005 memrun.c
pi#raspberrypi400:~/memrun/C $
The C script producing shown output is so small ...
#!/bin/bash
echo "foo"
./memrun <(gcc -o /dev/fd/1 -x c <(sed -n "/^\/\*\*$/,\$p" $0)) 42
exit
/**
*/
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("bar %s\n", argc>1 ? argv[1] : "(undef)");
for(int c=getchar(); EOF!=c; c=getchar()) { putchar(c); }
return 0;
}
P.S:
I added tcc's "-run" option to gcc and g++, for details see:
https://github.com/Hermann-SW/memrun/tree/master/C#adding-tcc--run-option-to-gcc-and-g
Just nice, and nothing gets stored in filesystem:
pi#raspberrypi400:~/memrun/C $ uname -a | g++ -O3 -Wall -run demo.cpp 42
bar 42
Linux raspberrypi400 5.10.60-v7l+ #1449 SMP Wed Aug 25 15:00:44 BST 2021 armv7l GNU/Linux
pi#raspberrypi400:~/memrun/C $
I am new to Tcl scripting and would like to use C to embed Tcl codes.
This is the code that I have copied from a website to test the Tcl-C working.
test.c
#include <stdio.h>
#include <tcl.h>
void main ()
{
Tcl_Interp *myinterp;
char *action = "set a [expr 5 * 8]; puts $a";
int status;
printf ("Your Program will run ... \n");
myinterp = Tcl_CreateInterp();
status = Tcl_Eval(myinterp,action);
printf ("Your Program has completed\n");
getch();
}
I am using MinGW to compile this file.
I have copied the contents of the C:\Tcl\include folder into the C:\MinGW\include folder as well.
My gcc command for compiling :
gcc -o test.exe test.c
The error message shown :
C:\Users\user\AppData\Local\Temp\ccEHJKCb.o:tcl_connection_test.c:(.text+0x23): undefined reference to `_imp__Tcl_CreateInterp'
C:\Users\user\AppData\Local\Temp\ccEHJKCb.o:tcl_connection_test.c:(.text+0x3d): undefined reference to `_imp__Tcl_Eval'
c:/mingw/bin/../lib/gcc/mingw32/4.8.1/../../../../mingw32/bin/ld.exe: C:\Users\user\AppData\Local\Temp\ccEHJKCb.o: bad reloc address 0x20 in section `.eh_frame'
c:/mingw/bin/../lib/gcc/mingw32/4.8.1/../../../../mingw32/bin/ld.exe: final link failed: Invalid operation
collect2.exe: error: ld returned 1 exit status
I don't seem to have any libtcl file in the Tcl folder.
The Tcl version is ActiveTcl 8.5.15.0.297577.
Any help would be really appreciated.
Your example how to embed Tcl is outdated, and you are missing certain things in your link line (-ltcl85 for example). If you simply add -ltcl85 to your link line it should start to work.
It does not work in your case, because you installed the x64 (64-Bit version) of ActiveTcl, which provides x64 dlls, not 32-Bit ones. But the standard mingw gcc only works with 32-Bit libraries.
So to get this to work:
Download the 32-Bit ActiveTcl distribution
Compile your code with gcc -o test.exe test.c -Lc:/tcl/lib -Ic:/tcl/include -ltcl86
Adjust your path so the c:\tcl\bin\tcl86.dll is found in PATH, make also sure Tcl finds its libdir (set TCL_LIBRARY=c:\tcl\lib\tcl8.6)
run your program
But for more complex examples, you still need to initialise the library and a do some boilerplate code, so please call Tcl_FindExecutable(argv[0]); before the call to Tcl_CreateInterp() otherwise a few commands (e.g. clock might just not work as expected).
Have a look at http://www.tcl.tk/cgi-bin/tct/tip/66.html for some more details. Also have a look at the Tcl source distribution and the source for the tclsh shell.
You're very close to getting it right.
The Tcler's Wiki has a few examples, some of which are very confusing to be frank, but this one from this page is the best I've spotted recently. (The comments are mine.)
#include <stdlib.h>
#include <tcl.h>
int main (int argc, char *argv[]) {
Tcl_Interp *interp;
const char *script = "proc p1 a { puts $a }";
// Initialize the Tcl library; ***STRONGLY RECOMMENDED***
Tcl_FindExecutable(argv[0]);
// Create the interpreter, the execution context
interp = Tcl_CreateInterp();
// Initialise the interpreter
if (TCL_OK != Tcl_Init(interp)) {
fprintf(stderr, "Tcl_Init error: %s\n", Tcl_GetStringResult(interp));
exit(EXIT_FAILURE);
}
// Define a procedure
Tcl_Eval(interp, script);
fprintf(stderr, "res 1: %s\n", Tcl_GetStringResult(interp));
// Check if the procedure exists
Tcl_Eval(interp, "puts [info commands p*]");
fprintf(stderr, "res 2: %s\n", Tcl_GetStringResult(interp));
// Call the procedure
Tcl_Eval(interp, "p1 abc");
fprintf(stderr, "res 3: %s\n", Tcl_GetStringResult(interp));
// We could use Tcl_DeleteInterpreter to clean up here, but why bother?
return EXIT_SUCCESS;
}
What else were you missing? Simple. You forgot to tell the C compiler to use the Tcl library when building the executable; the compiler (or, more strictly, the linker) is in places a stupid piece of code. The exact option to use to get the linker to add the library in will depend on your system configuration, but is probably going to be -ltcl, -ltcl8.5 or -ltcl8.6; which it is depends on the filename and all sorts of things that we can't check exactly without being on your system. The names do fit a simple pattern though.
It's also possible that you might need to pass the -L option in to tell the linker about additional library locations. (There's an equivalent -I for telling the compiler where to find include files, so you don't have to copy everything into one gigantic unmanageable directory.)
The order of arguments can matter. Libraries should be listed after the source file:
gcc -o test.exe test.c -L/mingw/path/to/library/directory -ltcl86
(If you're using old, unsupported versions of Tcl — why would you do that?! — then the code above won't work because Tcl_Eval then took a writable string. But that was fixed many years ago and upgrading to a current version is the fix.)