Opened 12 months ago

Last modified 5 weeks ago

#68638 assigned defect

bash @5.2.15: When a "command not found" situation happens, Bash prints errors and ends with "Abort Trap: 6" instead of printing "Command not found"

Reported by: some1so Owned by: raimue (Rainer Müller)
Priority: Normal Milestone:
Component: ports Version:
Keywords: Cc: mascguy (Christopher Nielsen), joel-coffman (Joel Coffman), kpreid (Kevin Reid), i0ntempest
Port: bash

Description (last modified by some1so)

For example, using 'derp' as a fake command (macports 5.2.15(1)-release):

~ $ derp
objc[75690]: +[__SwiftNativeNSStringBase initialize] may have been in progress in another thread when fork() was called.
objc[75690]: +[__SwiftNativeNSStringBase initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.
Abort trap: 6

expected behavior (Apple supplied 3.2.57):

~ $ derp
bash: derp: command not found
~ $ 

Change History (31)

comment:1 Changed 12 months ago by some1so

Description: modified (diff)

comment:2 Changed 12 months ago by ryandesign (Ryan Carsten Schmidt)

Owner: set to raimue
Status: newassigned
Summary: When a "command not found" situation happens, Bash prints errors and ends with "Abort Trap: 6" instead of printing "Command not found"bash @5.2.15: When a "command not found" situation happens, Bash prints errors and ends with "Abort Trap: 6" instead of printing "Command not found"

comment:3 Changed 12 months ago by raimue (Rainer Müller)

Do you have anything configured as PROMPT_COMMAND in bash that could cause this? Can you reproduce the issue also without your custom bashrc, i.e. bash --norc --noprofile?

comment:4 Changed 12 months ago by some1so

Hello

I tested this again. I'm using Apple Terminal.app, so I went into settings and changed the shell to "/opt/local/bin/bash --noprofile --norc". I then created a new window, and tested, and got a good result ("Command not found").

I then renamed my local bash_profile and bashrc files to get them out of the way, and set Terminal.app default shell to "/opt/local/bin/bash", opened a new window and tested again, and got a bad result (the same Swift error).

Next, I switch Terminal.app default shell to use the system default (/bin/bash). Open a new window and test. Good result. I then start a new session in the shell by executing "/opt/local/bin/bash", and test again. Good result.

So the only way to cause the error is to set Terminal.app to use "/opt/local/bin/bash".

Maybe of interest - when I am in a session and getting the error, if I use "Ctrl-X Ctrl-V" to print the shell version, and then test again, I get a good result. Somehow that clears whatever is causing the error.

comment:5 Changed 12 months ago by kickingvegas (Charles Choi)

FWIW, I'm seeing the same issue. Apparently setting Terminal.app to use /opt/local/bin/bash will emit the following message for unknown commands entered.

$ jsdf
objc[53830]: +[__SwiftNativeNSStringBase initialize] may have been in progress in another thread when fork() was called.
objc[53830]: +[__SwiftNativeNSStringBase initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.
Abort trap: 6

comment:6 Changed 12 months ago by raimue (Rainer Müller)

This must be coming from libc/libSystem and not from bash itself. You might be able to use export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES as a workaround (although I am not sure if this actually works when running as a login shell in Terminal.app).

Does this problem also occur when bash is started as login shell with /opt/local/bin/bash -l? That might make debugging easier as we would be able to attach dtruss to see which system calls are responsible for this behavior.

comment:7 Changed 12 months ago by raimue (Rainer Müller)

It might actually still be the same problem as comment:ticket:41248:13. Can you check whether LANG, LC_MESSAGES and LC_ALL are all unset in your environment?

comment:8 Changed 12 months ago by some1so

Hello, here are the results - I start off using /bin/bash and then start a login shell with the macports version.

~ $ echo $LANG, $LC_MESSAGES, $LC_ALL
en_US.UTF-8, ,
~ $ /opt/local/bin/bash -l
Restored session: Tue Nov 14 17:41:17 PST 2023
~ $ echo $LANG, $LC_MESSAGES, $LC_ALL
en_US.UTF-8, ,
~ $ derp
objc[7696]: +[__SwiftNativeNSStringBase initialize] may have been in progress in another thread when fork() was called.
objc[7696]: +[__SwiftNativeNSStringBase initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.
Abort trap: 6
~ $ export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
~ $ derp
objc[7697]: +[__SwiftNativeNSStringBase initialize] may have been in progress in another thread when fork() was called.
objc[7697]: +[__SwiftNativeNSStringBase initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.
Abort trap: 6
~ $ echo $OBJC_DISABLE_INITIALIZE_FORK_SAFETY
YES

comment:9 Changed 12 months ago by cculianu (Calin Culianu)

I have the same exact issue.

Any ideas? Should I rebuild bash from source?

comment:10 Changed 10 months ago by kickingvegas (Charles Choi)

Still seeing this issue with GNU bash, version 5.2.26(1)-release (aarch64-apple-darwin23.2.0)

comment:11 Changed 8 months ago by DarrenStone (Darren Stone)

Call stack:

Thread 0 Crashed::  Dispatch queue: com.apple.main-thread
0   libsystem_kernel.dylib        	       0x1864de72c __abort_with_payload + 8
1   libsystem_kernel.dylib        	       0x186503f88 abort_with_payload_wrapper_internal + 104
2   libsystem_kernel.dylib        	       0x186503f20 abort_with_reason + 32
3   libobjc.A.dylib               	       0x186179484 _objc_fatalv(unsigned long long, unsigned long long, char const*, char*) + 128
4   libobjc.A.dylib               	       0x186179404 _objc_fatal(char const*, ...) + 44
5   libobjc.A.dylib               	       0x18615fc1c performForkChildInitialize(objc_class*, objc_class*) + 400
6   libobjc.A.dylib               	       0x186146390 initializeNonMetaClass + 572
7   libobjc.A.dylib               	       0x1861461f0 initializeNonMetaClass + 156
8   libobjc.A.dylib               	       0x1861461f0 initializeNonMetaClass + 156
9   libobjc.A.dylib               	       0x186163304 initializeAndMaybeRelock(objc_class*, objc_object*, locker_mixin<lockdebug::lock_mixin<objc_lock_base_t>>&, bool) + 164
10  libobjc.A.dylib               	       0x186145dc4 lookUpImpOrForward + 892
11  libobjc.A.dylib               	       0x186145764 _objc_msgSend_uncached + 68
12  libswiftCore.dylib            	       0x1961145e0 type metadata accessor for __StringStorage + 24
13  libswiftCore.dylib            	       0x195eb63e8 String._bridgeToObjectiveCImpl() + 156
14  Foundation                    	       0x187911930 specialized LocaleCache.preferredLanguages(forCurrentUser:) + 76
15  Foundation                    	       0x187afd428 @objc static NSLocale._preferredLanguagesForCurrentUser(_:) + 44
16  CoreFoundation                	       0x1865ebbe4 CFLocaleCopyPreferredLanguages + 28
17  libintl.8.dylib               	       0x100ad93dc _libintl_language_preferences_default + 68
18  libintl.8.dylib               	       0x100ad7d40 libintl_dcigettext + 812
19  bash                          	       0x10057123c execute_disk_command + 696
20  bash                          	       0x10056c870 execute_simple_command + 3688
21  bash                          	       0x10056a8e0 execute_command_internal + 3056
22  bash                          	       0x100569c7c execute_command + 96
23  bash                          	       0x10055a77c reader_loop + 728
24  bash                          	       0x100559238 main + 4788
25  dyld                          	       0x1861910e0 start + 2360

This has been a long-standing problem. See this comment: comment:ticket:41248:13

However, the problem occurs all of the time, not just when LANG is unset.

Bash uses 'gettext' to generate localized error messages. If a child process calls 'gettext' before the parent process has initialzied the localization library then the child process will crash.

You can work around the problem by forcing the parent process to generate a localized error message. Once that is done, child processes can produce localized text correctly. For example, you can type ";" on a single line to generate a localized error message from the parent bash process:

$ foo
objc[10554]: +[__SwiftNativeNSStringBase initialize] may have been in progress in another thread when fork() was called.
objc[10554]: +[__SwiftNativeNSStringBase initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.
Abort trap: 6
$ ;
-bash: syntax error near unexpected token `;'
$ foo
-bash: foo: command not found
Last edited 7 months ago by ryandesign (Ryan Carsten Schmidt) (previous) (diff)

comment:12 Changed 8 months ago by mascguy (Christopher Nielsen)

Cc: mascguy added

comment:13 Changed 7 months ago by joel-coffman (Joel Coffman)

Cc: joel-coffman added

comment:14 Changed 7 months ago by msbit (Tom Sullivan)

This appears to be happening always with macOS 14, whereas it didn't happen on macOS 13.

Would it be worth adding the --with-included-gettext configure argument? This seems to "fix" the issue for me when compiled locally.

comment:15 Changed 7 months ago by ryandesign (Ryan Carsten Schmidt)

Is it the included gettext that's the problem? The bash port declares a dependency on the gettext port, so I would expect it to be using that gettext, not the included one.

comment:16 Changed 7 months ago by msbit (Tom Sullivan)

Is it the included gettext that's the problem?

Using the in-tree intl implementation(1) by passing --with-included-gettext to ./configure doesn't exhibit this problem. My guess is that it doesn't call into any Apple provided APIs (in this case CFLocaleCopyPreferredLanguages as shown in @DarrenStone's back trace) which have the specific check around Objc initialize during fork.

so I would expect it to be using that gettext, not the included one

Yep, it appears to be, the distributed binary looks something like this:

$ otool -L $(which bash)
/opt/local/bin/bash:
	/opt/local/lib/libncurses.6.dylib (compatibility version 6.0.0, current version 6.0.0)
	/opt/local/lib/libintl.8.dylib (compatibility version 12.0.0, current version 12.0.0)
	/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1858.112.0)
	/opt/local/lib/libiconv.2.dylib (compatibility version 9.0.0, current version 9.1.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.100.3)

(1) https://git.savannah.gnu.org/cgit/bash.git/tree/lib/intl?h=bash-5.2

comment:17 Changed 7 months ago by bradleyCPA (B. Holder)

That seems like it would work until they rev the in-tree gettext in the next release. https://git.savannah.gnu.org/cgit/bash.git/tree/lib/intl?h=bash-5.3-testing

Version 0, edited 7 months ago by bradleyCPA (B. Holder) (next)

comment:18 Changed 6 months ago by msbit (Tom Sullivan)

That seems like it would work until they rev the in-tree gettext in the next release. After which it would once again be broken.

Is there anything specifically in that branch that would cause that? I'm able to get a failing build by invoking:

LDFLAGS=-L/opt/local/lib ./configure

and a working build by invoking:

LDFLAGS=-L/opt/local/lib ./configure --with-included-gettext

on each of the following branches/tags

  • devel
  • bash-5.3-alpha
  • bash-5.3-testing

Test script:

#!/usr/bin/env bash

set -eu

git status --porcelain --ignored=matching | grep '^!!' | awk '{print $2}' | xargs rm -rf
git checkout -- .

LDFLAGS=-L/opt/local/lib ./configure --silent ${@}
make --jobs 12 --silent
otool -L bash
./bash -l

comment:19 Changed 6 months ago by kencu (Ken)

the presumption is that the gettext included in bash is old, and once it is updated to be as new as the one currently in macports, it will exhibit the same error.

comment:20 Changed 6 months ago by ednl (Ewoud Dronkert)

Same for me on an M1 with everything up-to-date: Sonoma 14.4.1, port 2.9.3, bash 5.2.26_0, gettext 0.22.5_0. Hopefully a definitive fix can be decided upon.

comment:21 in reply to:  16 ; Changed 6 months ago by ryandesign (Ryan Carsten Schmidt)

Replying to msbit:

Is it the included gettext that's the problem?

Using the in-tree intl implementation(1) by passing --with-included-gettext to ./configure doesn't exhibit this problem.

Sorry, I misread your suggestion. I thought you were suggesting --without-included-gettext which didn't make sense to me.

Using --with-included-gettext would be undesirable. We want to use MacPorts gettext which should be the most up-to-date and thus contain the most bug fixes. If there is a bug in this latest gettext that's causing this, let's fix it, or rather, report it to the developers of gettext so they can fix it and release a new version and we can then update to it.

comment:22 Changed 6 months ago by bradleyCPA (B. Holder)

I suppose the problem lies at the intersection of gettext() usage after fork() and before exec() in bash [execute_cmd.c in bash source] in combination with setlocale() function replacement [libintl.h, "define setlocale libintl_setlocale", gettext-runtime/intl/setlocale.c in gettext source]. It seems that there must be some scenario or scenarios where calling into Apple's APIs in the gettext-provided setlocale() replacement spawns a thread so that after the fork() [in bash] calling back into Apple's APIs using CFLocaleCopyPreferredLanguages (or similar) in the gettext() call results in the forked process terminating.

comment:23 in reply to:  19 Changed 6 months ago by msbit (Tom Sullivan)

Replying to kencu:

the presumption is that the gettext included in bash is old, and once it is updated to be as new as the one currently in macports, it will exhibit the same error.

👍

comment:24 in reply to:  21 Changed 6 months ago by msbit (Tom Sullivan)

Replying to ryandesign:

Replying to msbit:

Is it the included gettext that's the problem?

Using the in-tree intl implementation(1) by passing --with-included-gettext to ./configure doesn't exhibit this problem.

Sorry, I misread your suggestion. I thought you were suggesting --without-included-gettext which didn't make sense to me.

Using --with-included-gettext would be undesirable. We want to use MacPorts gettext which should be the most up-to-date and thus contain the most bug fixes. If there is a bug in this latest gettext that's causing this, let's fix it, or rather, report it to the developers of gettext so they can fix it and release a new version and we can then update to it.

👍

comment:25 in reply to:  22 Changed 6 months ago by msbit (Tom Sullivan)

Replying to bradleyCPA:

I suppose the problem lies at the intersection of gettext() usage after fork() and before exec() in bash [execute_cmd.c in bash source] in combination with setlocale() function replacement [libintl.h, "define setlocale libintl_setlocale", gettext-runtime/intl/setlocale.c in gettext source]. It seems that there must be some scenario or scenarios where calling into Apple's APIs in the gettext-provided setlocale() replacement spawns a thread so that after the fork() [in bash] calling back into Apple's APIs using CFLocaleCopyPreferredLanguages (or similar) in the gettext() call results in the forked process terminating.

From what I can tell, this is what happens:

int main() {
  // via bash`reset_locale_vars > libintl.8.dylib`libintl_setlocale > .`_libintl_locale_name_default
  CFPreferencesCopyAppValue(CFSTR("AppleLocale"), kCFPreferencesCurrentApplication);
  if (fork() == 0) {
    // via bash`execute_disk_command > libintl.8.dylib`libintl_gettext > .`libintl_dcgettext > .`libintl_dcigettext > .`guess_category_value > .`_libintl_language_preferences_default
    CFLocaleCopyPreferredLanguages();
  }
}

CFPreferencesCopyAppValue spins up a thread (one of the GCD worker threads) which triggers this forced crash whenever an objc +initialize method is called (as occurs within CFLocaleCopyPreferredLanguages).

comment:26 Changed 6 months ago by bradleyCPA (B. Holder)

That looks pretty spot on to me. The larger problem is bash is now forking [and doing unsafe things -- https://www.man7.org/linux/man-pages/man2/fork.2.html -- in the forked process before exec()] while multithreaded which is a barmy idea and probably a completely unintended side-effect of relying on gnu gettext.

To come back to this, I'd just add that I've never actually seen the bad behavior referenced here occur myself but maybe you could do something like:

#include <unistd.h>
#include <CoreFoundation/CFPreferences.h>
#include <objc/objc-runtime.h>

int main() {

    Class __SwiftNativeNSStringBase = objc_getClass("__SwiftNativeNSStringBase");
    id funAlloc = ((id (*)(Class, SEL))objc_msgSend) (__SwiftNativeNSStringBase, sel_registerName("alloc"));

    // via bash`reset_locale_vars > libintl.8.dylib`libintl_setlocale > .`_libintl_locale_name_default
    CFTypeRef preferences = CFPreferencesCopyAppValue(CFSTR("AppleLocale"), kCFPreferencesCurrentApplication);
    if (fork()== 0) {
        // via bash`execute_disk_command > libintl.8.dylib`libintl_gettext > .`libintl_dcgettext > .`libintl_dcigettext > .`guess_category_value > .`_libintl_language_preferences_default
        CFArrayRef prefArray = CFLocaleCopyPreferredLanguages();
    }
}
// compile like clang -g -framework Foundation -o main main.c
// run like OBJC_PRINT_INITIALIZE_METHODS=YES ./main 2>&1 | grep "__SwiftNativeNSStringBase"

to force the __SwiftNativeNSStringBase class initializer to run before the fork(). I'd guess that the workaround in comment:11 does something like that but not sure and YMMV.

Last edited 6 months ago by ryandesign (Ryan Carsten Schmidt) (previous) (diff)

comment:27 Changed 4 months ago by kpreid (Kevin Reid)

Cc: kpreid added

comment:28 Changed 2 months ago by jamesderlin (James D. Lin)

For anyone else looking for a simple, automatic workaround:

The workaround from comment:11 (running ; first) worked for me, but it's not obvious how to suppress the error message so that it could be done automatically in from .bashrc. The same thing applies to the workaround from https://trac.macports.org/ticket/41248#comment:11 (running trap [a-z] first). However, understanding that you just need to get bash to print a localized message (not necessarily an error message), generating the help to most bash builtins also should work (e.g. cd --help, set --help, etc.), and those get printed to stdout which can be easily redirected to /dev/null. In my .bashrc, I use: help help > /dev/null (along with a comment referencing this bug).

comment:29 in reply to:  28 Changed 8 weeks ago by i0ntempest

Replying to jamesderlin:

For anyone else looking for a simple, automatic workaround:

The workaround from comment:11 (running ; first) worked for me, but it's not obvious how to suppress the error message so that it could be done automatically in from .bashrc. The same thing applies to the workaround from https://trac.macports.org/ticket/41248#comment:11 (running trap [a-z] first). However, understanding that you just need to get bash to print a localized message (not necessarily an error message), generating the help to most bash builtins also should work (e.g. cd --help, set --help, etc.), and those get printed to stdout which can be easily redirected to /dev/null. In my .bashrc, I use: help help > /dev/null (along with a comment referencing this bug).

Does not seem to work for me. I put the command into .profile but I still get a crash when I run a nonexistent command. Running the command in the shell after initialization does work though.

comment:30 Changed 8 weeks ago by i0ntempest

Cc: i0ntempest added

comment:31 Changed 5 weeks ago by bradleyCPA (B. Holder)

You want https://git.savannah.gnu.org/cgit/bash.git/commit/?h=devel&id=b3d8c8a4e7c5417d986f93f646ea740cb13c08d7. Specifically, the init_notfound_str junk, probably, I haven't tested. I could never reproduce anyway.

Note: See TracTickets for help on using tickets.